Skip to content
Open
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
102 changes: 102 additions & 0 deletions packages/payload/src/collections/config/listSearchableFields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { Config } from '../../config/types.js'
import type { CollectionConfig } from '../../index.js'

import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
import { sanitizeCollection } from './sanitize.js'
import { describe, it, expect } from 'vitest'

describe('sanitize - collections -', () => {
const config = {
collections: [],
globals: [],
} as Partial<Config>

describe('validate listSearchableFields -', () => {
const defaultCollection: CollectionConfig = {
slug: 'collection-with-defaults',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'slug',
type: 'text',
},
],
}

it('should throw on non-existent field', () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
listSearchableFields: ['title', 'nonExistentField'],
},
}
expect(() => {
sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).toThrow(InvalidConfiguration)
})

it('should not throw when all fields exist', () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
listSearchableFields: ['title', 'slug'],
},
}
expect(() => {
sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})

it('should not throw on default field: id', () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
admin: {
listSearchableFields: ['id'],
},
}
expect(() => {
sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})

it('should not throw when listSearchableFields is undefined', () => {
const collectionConfig: CollectionConfig = {
...defaultCollection,
}
expect(() => {
sanitizeCollection(
// @ts-expect-error
{
...config,
collections: [collectionConfig],
},
collectionConfig,
)
}).not.toThrow()
})
})
})
47 changes: 47 additions & 0 deletions packages/payload/src/collections/config/listSearchableFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { CollectionConfig } from '../../index.js'

import { InvalidConfiguration } from '../../errors/InvalidConfiguration.js'
import { fieldAffectsData } from '../../fields/config/types.js'
import { flattenTopLevelFields } from '../../utilities/flattenTopLevelFields.js'

/**
* Validate listSearchableFields for collections.
*
* Previously, specifying a field name in `admin.listSearchableFields` that does not
* exist on the collection would not throw until the field was actually queried
* (e.g. when searching in the List View), and the resulting error was only
* ever surfaced in production builds - in development it silently failed to
* error at all. Validating eagerly here, at config sanitization time, ensures a
* clear, actionable error is thrown consistently in every environment.
*/
export const validateListSearchableFields = (config: CollectionConfig) => {
if (!config.admin?.listSearchableFields?.length) {
return
}

const fields = flattenTopLevelFields(config.fields)

for (const fieldName of config.admin.listSearchableFields) {
if (fieldName === 'id') {
continue
}

// Only validate the top-level segment of the path - relationship/join
// paths such as `category.title` are resolved deeper in the query and
// are not flattened top-level fields of this collection.
const topLevelFieldName = fieldName.split('.')[0]

const searchableField = fields.find((field) => {
if (fieldAffectsData(field)) {
return field.name === topLevelFieldName
}
return false
})

if (!searchableField) {
throw new InvalidConfiguration(
`The field "${fieldName}" specified in "admin.listSearchableFields" does not exist in the collection "${config.slug}"`,
)
}
}
}
2 changes: 2 additions & 0 deletions packages/payload/src/collections/config/sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { baseVersionFields } from '../../versions/baseFields.js'
import { versionDefaults } from '../../versions/defaults.js'
import { defaultCollectionEndpoints } from '../endpoints/index.js'
import { addDefaultsToAuthConfig, addDefaultsToCollectionConfig } from './defaults.js'
import { validateListSearchableFields } from './listSearchableFields.js'
import { sanitizeCompoundIndexes } from './sanitizeCompoundIndexes.js'
import { validateUseAsTitle } from './useAsTitle.js'

Expand Down Expand Up @@ -338,6 +339,7 @@ export const sanitizeCollection = (
}

validateUseAsTitle(sanitized)
validateListSearchableFields(sanitized)

const sanitizedConfig = sanitized as SanitizedCollectionConfig

Expand Down
Loading