allow special characters in an enum

This commit is contained in:
Pierre Noël 2022-01-21 15:32:44 +01:00
parent 3700b15f76
commit e85cfe73f0
9 changed files with 96 additions and 25 deletions

View File

@ -1,7 +1,9 @@
import _ from 'lodash';
import * as yup from 'yup';
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
import getTrad from '../../../utils/getTrad';
import getRelationType from '../../../utils/getRelationType';
import toGraphQLName from '../../../utils/toGraphQLName';
import {
alreadyUsedAttributeNames,
createTextShape,
@ -156,10 +158,23 @@ const types = {
},
})
.test({
name: 'valuesMatchesRegex',
message: errorsTrads.regex,
test: values => {
return values.every(val => val === '' || ENUM_REGEX.test(val));
name: 'valuesCollide',
message: 'Some values collide when normalized',
test(values) {
const normalizedEnum = values.map(toGraphQLName);
const duplicates = _(normalizedEnum)
.groupBy()
.pickBy(x => x.length > 1)
.keys()
.value();
if (duplicates.length) {
const message = `Some values collide when normalized: ${duplicates.join(', ')}`;
return this.createError({ message });
}
return true;
},
})
.test({

View File

@ -0,0 +1,10 @@
import slugify from '@sindresorhus/slugify';
const toGraphQLName = value =>
slugify(value, {
decamelize: false,
lowercase: false,
separator: '_',
});
export default toGraphQLName;

View File

@ -19,7 +19,6 @@ const validators = {
const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$');
const COLLECTION_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
const CATEGORY_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$');
const ICON_REGEX = new RegExp('^[A-Za-z0-9][-A-Za-z0-9]*$');
const UID_REGEX = new RegExp('^[A-Za-z0-9-_.~]*$');
@ -59,12 +58,6 @@ const isValidKey = key => ({
test: () => NAME_REGEX.test(key),
});
const isValidEnum = {
name: 'isValidEnum',
message: '${path} must match the following regex: ' + ENUM_REGEX,
test: val => val === '' || ENUM_REGEX.test(val),
};
const areEnumValuesUnique = {
name: 'areEnumValuesUnique',
message: '${path} cannot contain duplicate values',
@ -112,7 +105,6 @@ module.exports = {
isValidName,
isValidIcon,
isValidKey,
isValidEnum,
isValidUID,
isValidRegExpPattern,
};

View File

@ -10,7 +10,6 @@ const {
areEnumValuesUnique,
isValidDefaultJSON,
isValidName,
isValidEnum,
isValidUID,
isValidRegExpPattern,
} = require('./common');
@ -134,12 +133,7 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
return {
enum: yup
.array()
.of(
yup
.string()
.test(isValidEnum)
.required()
)
.of(yup.string().required())
.min(1)
.test(areEnumValuesUnique)
.required(),

View File

@ -1,7 +1,7 @@
'use strict';
const { keyBy, mapValues } = require('lodash');
const { yup } = require('@strapi/utils');
const _ = require('lodash');
const { yup, toGraphQLName } = require('@strapi/utils');
const LIFECYCLES = [
'beforeCreate',
@ -24,7 +24,7 @@ const LIFECYCLES = [
'afterDeleteMany',
];
const lifecyclesShape = mapValues(keyBy(LIFECYCLES), () =>
const lifecyclesShape = _.mapValues(_.keyBy(LIFECYCLES), () =>
yup
.mixed()
.nullable()
@ -47,6 +47,33 @@ const contentTypeSchemaValidator = yup.object().shape({
.required(),
})
.required(),
attributes: yup.object().test({
name: 'valuesCollide',
message: 'Some values collide when normalized',
test(attributes) {
for (const attrName in attributes) {
const attr = attributes[attrName];
if (attr.type === 'enumeration') {
const normalizedEnum = attr.enum.map(toGraphQLName);
const duplicates = _(normalizedEnum)
.groupBy()
.pickBy(x => x.length > 1)
.keys()
.value();
if (duplicates.length) {
const message = `Some enum values of the field '${attrName}' collide when normalized: ${duplicates.join(
', '
)}. Please modify your enumeration.`;
return this.createError({ message });
}
}
}
return true;
},
}),
}),
actions: yup.object().onlyContainsFunctions(),
lifecycles: yup

View File

@ -6,6 +6,7 @@ const {
stringEquals,
getCommonBeginning,
getCommonPath,
toGraphQLName,
} = require('../string-formatting');
describe('string-formatting', () => {
@ -97,4 +98,27 @@ describe('string-formatting', () => {
expect(result).toBe(expectedResult);
});
});
describe('toGraphQLName', () => {
test.each([
['', ''],
['a', 'a'],
['aa', 'aa'],
['aBa', 'aBa'],
['ABa', 'ABa'],
['ABA', 'ABA'],
['a a', 'a_a'],
['aa aa', 'aa_aa'],
['aBa aBa', 'aBa_aBa'],
['ABa ABa', 'ABa_ABa'],
['ABA ABA', 'ABA_ABA'],
['û', 'u'],
['Û', 'U'],
['München', 'Muenchen'],
['Baden-Württemberg', 'Baden_Wuerttemberg'],
['test_test', 'test_test'],
])('%s => %s', (string, expectedResult) => {
expect(toGraphQLName(string)).toBe(expectedResult);
});
});
});

View File

@ -19,6 +19,7 @@ const {
stringEquals,
isKebabCase,
isCamelCase,
toGraphQLName,
} = require('./string-formatting');
const { removeUndefined } = require('./object-formatting');
const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
@ -47,6 +48,7 @@ module.exports = {
traverseEntity,
parseType,
nameToSlug,
toGraphQLName,
nameToCollectionName,
getCommonBeginning,
getConfigUrls,

View File

@ -6,6 +6,13 @@ const nameToSlug = (name, options = { separator: '-' }) => slugify(name, options
const nameToCollectionName = name => slugify(name, { separator: '_' });
const toGraphQLName = value =>
slugify(value, {
decamelize: false,
lowercase: false,
separator: '_',
});
const getCommonBeginning = (...strings) =>
_.takeWhile(strings[0], (char, index) => strings.every(string => string[index] === char)).join(
''
@ -46,4 +53,5 @@ module.exports = {
stringEquals,
isCamelCase,
isKebabCase,
toGraphQLName,
};

View File

@ -2,6 +2,7 @@
const { enumType } = require('nexus');
const { set } = require('lodash/fp');
const { toGraphQLName } = require('@strapi/utils');
/**
* Build a Nexus enum type from a Strapi enum attribute
@ -13,9 +14,7 @@ const { set } = require('lodash/fp');
const buildEnumTypeDefinition = (definition, name) => {
return enumType({
name,
// In Strapi V3, the key of an enum is also its value
// todo[V4]: allow passing an object of key/value instead of an array
members: definition.enum.reduce((acc, value) => set(value, value, acc), {}),
members: definition.enum.reduce((acc, value) => set(toGraphQLName(value), value, acc), {}),
});
};