Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export type SchemaValidationErrorType =
* Schema composition keywords (allOf, anyOf, oneOf, not)
* These keywords apply subschemas in a logical manner according to JSON Schema spec
*/
| 'anyOf'
| 'oneOf'
| 'not'
| 'anyOf'
/**
* String validation keywords
*/
Expand Down
4 changes: 2 additions & 2 deletions src/errors/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export function getErrorMessage(
case 'enum':
return `The option "${valueToString(value)}" is not valid.`
// Schema composition
case 'anyOf':
return `The option "${valueToString(value)}" is not valid.`
case 'oneOf':
return `The option "${valueToString(value)}" is not valid.`
case 'anyOf':
return `The option "${valueToString(value)}" is not valid.`
case 'not':
return 'The value must not satisfy the provided schema'
// String validation
Expand Down
5 changes: 4 additions & 1 deletion src/field/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,10 @@ export function buildFieldSchema({
})
}

addOptions(field, schema)
if (name !== 'root') {
addOptions(field, schema)
}

addFields(field, schema, originalSchema)

return field
Expand Down
50 changes: 39 additions & 11 deletions src/validation/composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,49 @@ export function validateAnyOf(
return []
}

// If the path is not empty, we are validating a nested schema (property).
// In this case, we need to check if any of the sub-schemas are valid. If not, we indicate
// the field is invalid with a generic (anyOf) error.
if (path.length !== 0) {
for (const subSchema of schema.anyOf) {
const errors = validateSchema(value, subSchema, options, path, jsonLogicContext)
if (errors.length === 0) {
return []
}
}

return [
{
path,
validation: 'anyOf',
schema,
value,
},
]
}

const errorGroups: ValidationError[][] = []

// If the path is empty, we are validating the root schema.
// If the number of failed rules is less than the number of rules, it means that the
// "any of" condition is met, so we return an empty array. Otherwise, we return the flattened errors.
for (const subSchema of schema.anyOf) {
const errors = validateSchema(value, subSchema, options, path, jsonLogicContext)
if (errors.length === 0) {
return []
const schemaErrors = validateSchema(value, subSchema, options, path, jsonLogicContext)
// If the schema is not valid, add the errors to the errorGroups array
if (schemaErrors.length !== 0) {
errorGroups.push(schemaErrors)
}
}

return [
{
path,
validation: 'anyOf',
schema,
value,
},
]
const anyConditionMet = errorGroups.length < schema.anyOf.length
if (anyConditionMet) {
return []
}
else {
// Reversing the errors to show the first error that occurred (in the addErrorMessages function,
// the last error is usually the one being displayed)
return errorGroups.flat().reverse()
}
}

/**
Expand Down
40 changes: 40 additions & 0 deletions test/errors/messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,46 @@ describe('validation error messages', () => {
})
})

it('shows field validation error messages for nested schemas in a root anyOf', () => {
const schema: JsfObjectSchema = {
type: 'object',
properties: {
field_a: {
type: 'string',
},
field_b: {
type: 'string',
},
},
anyOf: [
{ required: ['field_a'] },
{ required: ['field_b'] },
],
}
const form = createHeadlessForm(schema)

// Check that we get an error for both fields if none is provided
let result = form.handleValidation({ })

expect(result.formErrors).toMatchObject({
field_a: 'Required field',
field_b: 'Required field',
})

// Check that we get don't get an error if one of the fields is provided
result = form.handleValidation({
field_a: '123456',
})

expect(result.formErrors).toBeUndefined()

result = form.handleValidation({
field_b: '123456',
})

expect(result.formErrors).toBeUndefined()
})

it('shows oneOf validation error messages', () => {
const schema: JsfObjectSchema = {
type: 'object',
Expand Down
7 changes: 4 additions & 3 deletions test/validation/composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ describe('schema composition validators', () => {
it('should fail when value matches no schema', () => {
const value = 'too long'
const errors = validateSchema(value, schema)
expect(errors).toHaveLength(1)
expect(errors[0].validation).toBe('anyOf')
expect(errors).toHaveLength(2)
expect(errors[0].validation).toBe('type')
expect(errors[1].validation).toBe('maxLength')
})
})

Expand Down Expand Up @@ -225,7 +226,7 @@ describe('schema composition validators', () => {
it('should fail for non-null values', () => {
const errors = validateSchema(123, schema)
expect(errors).toHaveLength(1)
expect(errors[0].validation).toBe('anyOf')
expect(errors[0].validation).toBe('type')
})
})
})
Expand Down