diff --git a/docs/docs/guides/developer-guide/custom-fields/index.md b/docs/docs/guides/developer-guide/custom-fields/index.md index 10589226a2..7afa720de4 100644 --- a/docs/docs/guides/developer-guide/custom-fields/index.md +++ b/docs/docs/guides/developer-guide/custom-fields/index.md @@ -515,7 +515,7 @@ const config = { #### validate - + A custom validation function. If the value is valid, then the function should not return a value. If a string or LocalizedString array is returned, this is interpreted as an error message. @@ -563,7 +563,7 @@ const config = { name: 'partCode', type: 'string', // highlight-start - validate: async (value, injector) => { + validate: async (value, injector, ctx) => { const partCodeService = injector.get(PartCodeService); const isValid = await partCodeService.validateCode(value); if (!isValid) { diff --git a/packages/core/src/api/common/validate-custom-field-value.spec.ts b/packages/core/src/api/common/validate-custom-field-value.spec.ts index 49a1a51be1..d95fffb715 100644 --- a/packages/core/src/api/common/validate-custom-field-value.spec.ts +++ b/packages/core/src/api/common/validate-custom-field-value.spec.ts @@ -3,13 +3,14 @@ import { fail } from 'assert'; import { describe, expect, it } from 'vitest'; import { Injector } from '../../common/injector'; +import { RequestContext } from './request-context'; import { validateCustomFieldValue } from './validate-custom-field-value'; describe('validateCustomFieldValue()', () => { const injector = new Injector({} as any); - async function assertThrowsError(validateFn: () => Promise, message: string) { + async function assertThrowsError(validateFn: (() => Promise) | (() => void), message: string) { try { await validateFn(); fail('Should have thrown'); @@ -18,6 +19,8 @@ describe('validateCustomFieldValue()', () => { } } + const ctx = RequestContext.empty(); + describe('string & localeString', () => { const validate = (value: string) => () => validateCustomFieldValue( @@ -28,6 +31,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); it('passes valid pattern', async () => { @@ -53,6 +57,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); it('passes valid option', async () => { @@ -78,6 +83,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); it('passes valid range', async () => { @@ -104,6 +110,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); it('passes valid range', async () => { @@ -138,9 +145,14 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); - const validate2 = (value: string, languageCode: LanguageCode) => () => - validateCustomFieldValue( + const validate2 = (value: string, languageCode: LanguageCode) => () => { + const ctxWithLanguage = new RequestContext({ + languageCode, + apiType: 'admin', + } as any); + return validateCustomFieldValue( { name: 'test', type: 'string', @@ -155,8 +167,9 @@ describe('validateCustomFieldValue()', () => { }, value, injector, - languageCode, + ctxWithLanguage, ); + }; it('passes validate fn string', async () => { expect(validate1('valid')).not.toThrow(); @@ -192,6 +205,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); expect(validate([1, 2, 6])).not.toThrow(); @@ -209,6 +223,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); expect(validate(['small', 'large'])).not.toThrow(); @@ -230,6 +245,7 @@ describe('validateCustomFieldValue()', () => { }, value, injector, + ctx, ); expect(validate(['valid', 'valid'])).not.toThrow(); diff --git a/packages/core/src/api/common/validate-custom-field-value.ts b/packages/core/src/api/common/validate-custom-field-value.ts index 64417691de..b04b7a3518 100644 --- a/packages/core/src/api/common/validate-custom-field-value.ts +++ b/packages/core/src/api/common/validate-custom-field-value.ts @@ -12,6 +12,7 @@ import { StringCustomFieldConfig, TypedCustomFieldConfig, } from '../../config/custom-field/custom-field-types'; +import { RequestContext } from './request-context'; /** * Validates the value of a custom field input against any configured constraints. @@ -21,7 +22,7 @@ export async function validateCustomFieldValue( config: CustomFieldConfig, value: any | any[], injector: Injector, - languageCode?: LanguageCode, + ctx: RequestContext, ): Promise { if (config.readonly) { throw new UserInputError('error.field-invalid-readonly', { name: config.name }); @@ -40,7 +41,7 @@ export async function validateCustomFieldValue( } else { validateSingleValue(config, value); } - await validateCustomFunction(config as TypedCustomFieldConfig, value, injector, languageCode); + await validateCustomFunction(config as TypedCustomFieldConfig, value, injector, ctx); } function validateSingleValue(config: CustomFieldConfig, value: any) { @@ -70,15 +71,15 @@ async function validateCustomFunction config: T, value: any, injector: Injector, - languageCode?: LanguageCode, + ctx: RequestContext, ) { if (typeof config.validate === 'function') { - const error = await config.validate(value, injector); + const error = await config.validate(value, injector, ctx); if (typeof error === 'string') { throw new UserInputError(error); } if (Array.isArray(error)) { - const localizedError = error.find(e => e.languageCode === languageCode) || error[0]; + const localizedError = error.find(e => e.languageCode === ctx.languageCode) || error[0]; throw new UserInputError(localizedError.value); } } diff --git a/packages/core/src/api/config/get-custom-fields-config-without-interfaces.ts b/packages/core/src/api/config/get-custom-fields-config-without-interfaces.ts index d41e131f3d..42a343e838 100644 --- a/packages/core/src/api/config/get-custom-fields-config-without-interfaces.ts +++ b/packages/core/src/api/config/get-custom-fields-config-without-interfaces.ts @@ -1,6 +1,6 @@ import { GraphQLSchema, isInterfaceType } from 'graphql'; -import { CustomFields, CustomFieldConfig } from '../../config/custom-field/custom-field-types'; +import { CustomFieldConfig, CustomFields } from '../../config/custom-field/custom-field-types'; /** * @description @@ -23,7 +23,7 @@ export function getCustomFieldsConfigWithoutInterfaces( entries.splice(regionIndex, 1); for (const implementation of implementations.objects) { - entries.push([implementation.name, customFieldConfig.Region]); + entries.push([implementation.name, customFieldConfig.Region ?? []]); } } } diff --git a/packages/core/src/api/middleware/validate-custom-fields-interceptor.ts b/packages/core/src/api/middleware/validate-custom-fields-interceptor.ts index 7779fb6c2f..5ff534e47f 100644 --- a/packages/core/src/api/middleware/validate-custom-fields-interceptor.ts +++ b/packages/core/src/api/middleware/validate-custom-fields-interceptor.ts @@ -59,7 +59,7 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { : [variables[inputName]]; for (const inputVariable of inputVariables) { - await this.validateInput(typeName, ctx.languageCode, injector, inputVariable); + await this.validateInput(typeName, ctx, injector, inputVariable); } } } @@ -71,7 +71,7 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { private async validateInput( typeName: string, - languageCode: LanguageCode, + ctx: RequestContext, injector: Injector, variableValues?: { [key: string]: any }, ) { @@ -83,7 +83,7 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { // mutations. await this.validateCustomFieldsObject( this.configService.customFields.OrderLine, - languageCode, + ctx, variableValues, injector, ); @@ -91,7 +91,7 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { if (variableValues.customFields) { await this.validateCustomFieldsObject( customFieldConfig, - languageCode, + ctx, variableValues.customFields, injector, ); @@ -102,7 +102,7 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { if (translation.customFields) { await this.validateCustomFieldsObject( customFieldConfig, - languageCode, + ctx, translation.customFields, injector, ); @@ -114,14 +114,14 @@ export class ValidateCustomFieldsInterceptor implements NestInterceptor { private async validateCustomFieldsObject( customFieldConfig: CustomFieldConfig[], - languageCode: LanguageCode, + ctx: RequestContext, customFieldsObject: { [key: string]: any }, injector: Injector, ) { for (const [key, value] of Object.entries(customFieldsObject)) { const config = customFieldConfig.find(c => getGraphQlInputName(c) === key); if (config) { - await validateCustomFieldValue(config, value, injector, languageCode); + await validateCustomFieldValue(config, value, injector, ctx); } } } diff --git a/packages/core/src/config/custom-field/custom-field-types.ts b/packages/core/src/config/custom-field/custom-field-types.ts index 3aa6c2edc4..0f52100df8 100644 --- a/packages/core/src/config/custom-field/custom-field-types.ts +++ b/packages/core/src/config/custom-field/custom-field-types.ts @@ -19,6 +19,7 @@ import { UiComponentConfig, } from '@vendure/common/lib/shared-types'; +import { RequestContext } from '../../api/index'; import { Injector } from '../../common/injector'; import { VendureEntity } from '../../entity/base/base.entity'; @@ -61,6 +62,7 @@ export type TypedCustomSingleFieldConfig< validate?: ( value: DefaultValueType, injector: Injector, + ctx: RequestContext, ) => string | LocalizedString[] | void | Promise; }; @@ -172,7 +174,7 @@ export type CustomFields = { TaxRate?: CustomFieldConfig[]; User?: CustomFieldConfig[]; Zone?: CustomFieldConfig[]; -} & { [entity: string]: CustomFieldConfig[] | undefined }; +} & { [entity: string]: CustomFieldConfig[] }; /** * This interface should be implemented by any entity which can be extended