import _ from "lodash";
import {addMethod, array, defaultLocale, Flags, Message, TestContext} from 'yup';
// https://github.com/jquense/yup?tab=readme-ov-file#extending-built-in-schema-with-new-methods
// https://github.com/jquense/yup/issues/345
declare module 'yup' {
interface ArraySchema<TIn extends any[] | null | undefined, TContext, TDefault = undefined, TFlags extends Flags = ''> {
unique(props?: UniqueProps): this;
}
interface ArrayLocale {
unique?: Message<UniqueExtra>;
}
}
export type UniqueProps = {
/** if the item of array is object, fieldName used to associate a unique field */
fieldName?: string,
/** get the duplicated values */
verbose?: boolean,
/** custom message */
message?: Message<UniqueExtra>,
};
export type UniqueExtra = {
/** if {@link UniqueProps#fieldName} assigned, this will be fieldLabel */
itemLabel: string;
/** value join by ',' */
rawValue?: string;
};
function rawValue(key: string, record: Record<string, number[]>) {
return `${key}[${record[key].join(",")}]`;
}
addMethod(array, 'unique', function (props: UniqueProps = {}) {
return this.test({
name: "unique",
// @ts-ignore
message: props.message || defaultLocale.array.unique || "${path} must be unique, ${itemLabel}${rawValue} is duplicated!",
params: {itemLabel: "value", rawValue: ""} as UniqueExtra,
test(value: any[] | undefined, context: TestContext) {
if (!value || value.length <= 1) return true;
// arraySchema, itemSchema, fieldSchema
let itemSchema = context.schema.innerType;
const fieldName = props.fieldName;
if (fieldName) {
// itemSchema <- fieldSchema
itemSchema = itemSchema.fields[fieldName];
value = value.map((item: any) => item[fieldName]);
}
const itemLabel = itemSchema.spec.label;
if (props.verbose !== true) {
return value.length === new Set(value).size
|| context.createError({
params: {itemLabel,},
});
}
const grouped = _(value)
.map((item, index) => ({value: item, index}))
.groupBy('value')
.pickBy((value, _key) => value.length > 1)
.mapValues((value, _key) => value.map(item => item.index))
.value()
;
const keys = Object.keys(grouped);
if (keys.length > 0) {
return context.createError({
params: {itemLabel, rawValue: keys.map(key => rawValue(key, grouped)).join(','),},
});
}
return true;
}
});
});
users: [{name: 'one user', age: 23}, {name: 'two user', age: 28}]
. Want to add error to duplicate names. – Andrien