diff --git a/src/lib/error/bad-data-error.ts b/src/lib/error/bad-data-error.ts index 9c034ca92a..0f1e73ec2a 100644 --- a/src/lib/error/bad-data-error.ts +++ b/src/lib/error/bad-data-error.ts @@ -4,7 +4,7 @@ import getProp from 'lodash.get'; import { ApiErrorSchema, UnleashError } from './unleash-error'; type ValidationErrorDescription = { - description: string; + description?: string; message: string; path?: string; }; diff --git a/src/lib/features/feature-naming-pattern/feature-naming-validation.test.ts b/src/lib/features/feature-naming-pattern/feature-naming-validation.test.ts index 39f2a936cf..fe9a04ff98 100644 --- a/src/lib/features/feature-naming-pattern/feature-naming-validation.test.ts +++ b/src/lib/features/feature-naming-pattern/feature-naming-validation.test.ts @@ -60,6 +60,24 @@ describe('validate incoming feature naming data', () => { ).toMatchObject({ state: 'invalid' }); } }); + + test('feature naming data with a non-empty example but an empty pattern is invalid', () => { + expect( + checkFeatureNamingData({ + pattern: '', + example: 'example', + }), + ).toMatchObject({ state: 'invalid' }); + }); + + test('feature naming data with a non-empty description but an empty pattern is invalid', () => { + expect( + checkFeatureNamingData({ + pattern: '', + description: 'description', + }), + ).toMatchObject({ state: 'invalid' }); + }); }); describe('validate feature flag names against a pattern', () => { diff --git a/src/lib/features/feature-naming-pattern/feature-naming-validation.ts b/src/lib/features/feature-naming-pattern/feature-naming-validation.ts index 2c0ee94294..3bb7df0efc 100644 --- a/src/lib/features/feature-naming-pattern/feature-naming-validation.ts +++ b/src/lib/features/feature-naming-pattern/feature-naming-validation.ts @@ -4,25 +4,33 @@ const compileRegex = (pattern: string) => new RegExp(`^${pattern}$`); export const checkFeatureNamingData = ( featureNaming?: IFeatureNaming, -): { state: 'valid' } | { state: 'invalid'; reason: string } => { +): + | { state: 'valid' } + | { state: 'invalid'; reasons: [string, ...string[]] } => { if (featureNaming) { - const { pattern, example } = featureNaming; - if ( - pattern != null && - example && - !example.match(compileRegex(pattern)) - ) { - return { - state: 'invalid', - reason: `You've provided a feature flag naming example ("${example}") that doesn't match your feature flag naming pattern ("${pattern}"). Please provide an example that matches your supplied pattern. Bear in mind that the pattern must match the whole example, as if it were surrounded by '^' and "$".`, - }; + const { pattern, example, description } = featureNaming; + const errors: string[] = []; + if (pattern && example && !example.match(compileRegex(pattern))) { + errors.push( + `You've provided a feature flag naming example ("${example}") that doesn't match your feature flag naming pattern ("${pattern}"). Please provide an example that matches your supplied pattern. Bear in mind that the pattern must match the whole example, as if it were surrounded by '^' and "$".`, + ); } if (!pattern && example) { - return { - state: 'invalid', - reason: "You've provided a feature flag naming example, but no feature flag naming pattern. You must specify a pattern to use an example.", - }; + errors.push( + "You've provided a feature flag naming example, but no feature flag naming pattern. You must specify a pattern to use an example.", + ); + } + + if (!pattern && description) { + errors.push( + "You've provided a feature flag naming pattern description, but no feature flag naming pattern. You must have a pattern to use a description.", + ); + } + + const [first, ...rest] = errors; + if (first) { + return { state: 'invalid', reasons: [first, ...rest] }; } } diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index 155733dddd..45779aac81 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -176,7 +176,14 @@ export default class ProjectService { const validationResult = checkFeatureNamingData(featureNaming); if (validationResult.state === 'invalid') { - throw new BadDataError(validationResult.reason); + const [firstReason, ...remainingReasons] = + validationResult.reasons.map((message) => ({ + message, + })); + throw new BadDataError( + 'The feature naming pattern data you provided was invalid.', + [firstReason, ...remainingReasons], + ); } if (featureNaming.pattern && !featureNaming.example) {