fix: validate "null" value for point field as true when its not required (#12908)
### What?
This PR solves an issue with validation of the `point` field in Payload
CMS. If the value is `null` and the field is not required, the
validation will return `true` before trying to examine the contents of
the field
### Why?
If the point field is given a value, and saved, it is then impossible to
successfully "unset" the point field, either through the CMS UI or
through a hook like `beforeChange`. Trying to do so will throw this
error:
```
[17:09:41] ERROR: Cannot read properties of null (reading '0')
err: {
"type": "TypeError",
"message": "Cannot read properties of null (reading '0')",
"stack":
TypeError: Cannot read properties of null (reading '0')
at point (webpack-internal:///(rsc)/./node_modules/.pnpm/payload@3.43.0_graphql@16.10.0_typescript@5.7.3/node_modules/payload/dist/fields/validations.js:622:40)
```
because a value of `null` will not be changed to the default value of
`['','']`, which in any case does not pass MongoDB validation either.
```
[17:22:49] ERROR: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
err: {
"type": "CastError",
"message": "Cast to [Number] failed for value \"[ NaN, NaN ]\" (type string) at path \"location.coordinates.0\" because of \"CastError\"",
"stack":
CastError: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
at SchemaArray.cast (webpack-internal:///(rsc)/./node_modules/.pnpm/mongoose@8.15.1_@aws-sdk+credential-providers@3.778.0/node_modules/mongoose/lib/schema/array.js:414:15)
```
### How?
This adds a check to the top of the `point` validation function and
returns early before trying to examine the contents of the point field
---------
Co-authored-by: Dave Ryan <dmr@Daves-MacBook-Pro.local>
This commit is contained in:
@@ -937,11 +937,19 @@ export type PointFieldValidation = Validate<
|
||||
>
|
||||
|
||||
export const point: PointFieldValidation = (value = ['', ''], { req: { t }, required }) => {
|
||||
const lng = parseFloat(String(value![0]))
|
||||
const lat = parseFloat(String(value![1]))
|
||||
if (value === null) {
|
||||
if (required) {
|
||||
return t('validation:required')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const lng = parseFloat(String(value[0]))
|
||||
const lat = parseFloat(String(value[1]))
|
||||
if (
|
||||
required &&
|
||||
((value![0] && value![1] && typeof lng !== 'number' && typeof lat !== 'number') ||
|
||||
((value[0] && value[1] && typeof lng !== 'number' && typeof lat !== 'number') ||
|
||||
Number.isNaN(lng) ||
|
||||
Number.isNaN(lat) ||
|
||||
(Array.isArray(value) && value.length !== 2))
|
||||
@@ -949,7 +957,7 @@ export const point: PointFieldValidation = (value = ['', ''], { req: { t }, requ
|
||||
return t('validation:requiresTwoNumbers')
|
||||
}
|
||||
|
||||
if ((value![1] && Number.isNaN(lng)) || (value![0] && Number.isNaN(lat))) {
|
||||
if ((value[1] && Number.isNaN(lng)) || (value[0] && Number.isNaN(lat))) {
|
||||
return t('validation:invalidInput')
|
||||
}
|
||||
|
||||
|
||||
@@ -1396,6 +1396,58 @@ describe('Fields', () => {
|
||||
expect(doc.localized).toEqual(localized)
|
||||
expect(doc.group).toMatchObject(group)
|
||||
})
|
||||
|
||||
it('should throw validation error when "required" field is set to null', async () => {
|
||||
if (payload.db.name === 'sqlite') {
|
||||
return
|
||||
}
|
||||
// first create the point field
|
||||
doc = await payload.create({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
localized,
|
||||
point,
|
||||
},
|
||||
})
|
||||
|
||||
// try to update the required field to null
|
||||
await expect(() =>
|
||||
payload.update({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
point: null,
|
||||
},
|
||||
id: doc.id,
|
||||
}),
|
||||
).rejects.toThrow('The following field is invalid: Location')
|
||||
})
|
||||
|
||||
it('should not throw validation error when non-"required" field is set to null', async () => {
|
||||
if (payload.db.name === 'sqlite') {
|
||||
return
|
||||
}
|
||||
// first create the point field
|
||||
doc = await payload.create({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
localized,
|
||||
point,
|
||||
},
|
||||
})
|
||||
|
||||
expect(doc.localized).toEqual(localized)
|
||||
|
||||
// try to update the non-required field to null
|
||||
const updatedDoc = await payload.update({
|
||||
collection: 'point-fields',
|
||||
data: {
|
||||
localized: null,
|
||||
},
|
||||
id: doc.id,
|
||||
})
|
||||
|
||||
expect(updatedDoc.localized).toEqual(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkbox', () => {
|
||||
|
||||
Reference in New Issue
Block a user