Is it possible to DISPLAY IN MESSAGE such a format (as it should be) e.g. to a zip code in classValidator?
export function valIsPostalCode(locale:any, property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
propertyName,
name: 'valIsPostalCode',
target: object.constructor,
options: validationOptions,
constraints: [property],
validator: {
validate(value:any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
const realLocale = typeof locale === 'function' ? locale(relatedValue) : locale;
return isPostalCode(value, realLocale);
},
defaultMessage() {
return `${validationDict.classValidator.isZipCodeCorrect} like: ${PROPER_FORMAT}`;
},
},
});
};
}
Example: Enter the zip code in the correct format: XXX-XX
Related
I want to add splitArray type to Joi which converts string to an array using method Array.split. As you know, Array.split can split by argument, and I want to pass this argument during creation of the schema and not during it's usage.
So, currently I have this extension that allows me to split string to an array during validation with the use of helpers:
const splitArrayExt = joi => ({
base: joi.array(),
coerce: (value, helpers) => ({
value: value.split ? value.split(helpers.prefs.split ? helpers.prefs.split : /\s+/) : value
}),
type: 'splitArray',
)}
The problems is - I have to pass { split: <ANOTHER SPLIT ARG> } every time I call validate.
Is it possible to just do something like this:
const JoiBase = require('joi');
...
const Joi = JoiBase.extend(splitArrayExt);
const schema1 = Joi.splitArray().splitBy('|').items(Joi.string())
const schema2 = Joi.splitArray().splitBy(',').items(Joi.string())
schema1.validate('A|B,C') // values: [ 'A', 'B,C' ]
schema2.validate('A|B,C') // values: [ 'A|B', 'C' ]
So, anyway, I've figured it out:
const customJoi = Joi.extend(joi => ({
type: 'splitArray', // add type with custom name
base: joi.array(), // let's base it on Joi.array
messages: { // this is required to yell if we didn't get a string before parse
'split.base': '"value" must be a string',
},
coerce(value, helpers) { // this executes before 'validate' method is called, so we can convert string to an array
if (typeof(value) !== 'string') {
return { errors: helpers.error('split.base') };
}
const rule = helpers.schema.$_getRule('splitBy'); // this is needed to determent delimiter (default: /\s+/)
return { value: value.split(rule ? rule.args.delimiter : /\s+/) };
},
rules: {
splitBy: { // add new rule to set different delimiter from default
convert: true,
method(delimiter) {
return this.$_addRule({ name: 'splitBy', args: { delimiter } });
},
},
}
}));
...
// and voilà
customJoi.splitArray().items(customJoi.number()).validate('1 2\n3\t4')
// [ 1, 2, 3, 4 ]
customJoi.splitArray().splitBy('|').validate('A|B,C');
// [ 'A', 'B,C' ]
customJoi.splitArray().splitBy(',').validate('A|B,C');
// [ 'A|B', 'C' ]
I can’t find information on how to correctly return exactly the type of a certain property of the type, passing the key in another field.
Can someone tell me or point out what I'm doing wrong? :(
type TUser = {
age: number,
initials: {
name: string
}
}
type TRendererFunctionArgs<TValues> = {
value: TValues[keyof TValues],
}
type TRenderer<TValues> = {
name: keyof TValues,
renderer: ({ value }: TRendererFunctionArgs<TValues>) => string,
}
const renderer : TRenderer<TUser> = {
name: 'initials',
renderer: ({ value }) => value.name, // <--- There is problem because. value : value: number | {name: string}
}
Playground link
Here's an approach that uses a mapped type as an indexed access type:
type TUser = {
age: number,
initials: {
name: string
}
}
type TRenderer<TValues> = { [K in keyof TValues]-?: {
name: K;
renderer: ({ value }: { value: TValues[K] }) => string;
} }[keyof TValues];
const renderer : TRenderer<TUser> = {
name: 'initials',
renderer: ({ value }) => value.name
}
export function collectAttributes(element: Element): AttributesInterface {
return Array.from(element.attributes, ({ name, value }) => [
name,
value,
]).reduce((a, [key, val]) => {
if (key.includes("#")) {
let handlerName = key.replace("#", "")
a.handlers.push([handlerName, val])
} else if (key.startsWith("s:")) {
let styleProp = key.replace("s:", "")
a.bindedStyle.push([styleProp, val])
} else if (key.startsWith("c:")) {
let classProp = key.replace("c:", "");
a.bindedClasses.push([classProp, val])
} else if (key.startsWith("a:")) {
let attributeProp = key.replace("a:", "");
a.bindedAttr.push([attributeProp, val])
} else if (key === "if") {
a.show = val
} else if (key.startsWith("p:")) {
let prop = key.replace("p:", "");
a.props[prop] = val // <------ HERE
} else {
a.attr.push([key, val])
}
return a
}, {
attr: [],
handlers: [],
bindedStyle: [],
bindedAttr: [],
bindedClasses: [],
show: null,
visible: true,
props: {},
parent: element.parentElement,
index: element.parentElement ? Array.prototype.indexOf.call(element.parentElement.children, element) : 0
})
}
export interface AttributesInterface {
attr: string[][],
handlers: string[][],
bindedStyle: string[][],
bindedAttr: string[][]
bindedClasses: string[][],
show: string,
visible: Boolean,
parent: HTMLElement,
index: number,
props: { [key: string]: string }
}
I have the problem that i cannot set a new property. I have tried it to set { [key: string]: string } to my props object but it doesnt seems to fix the problem. I still get the error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
No index signature with a parameter of type 'string' was found on type '{}'.
I have fixed it:
I have added the AttributesInterface to the accumulator of the reduce function
.reduce((a: AttributesInterface, [key, val]) => {
I have to check if some items object follow a certain format as below. These items are input to a component and I want to check the validity of the input.
I already wrote some code to check for the validity of the items, but I want to know if there could be a better way to write it?
Thanks!
{
main: {
id: string,
name: string,
},
drilldowns: [
{
id: string,
name: string,
elements: [
{
id: string,
name: string,
}
],
}
],
}
export const isValidItem = (item) => {
if (!item.main || (item.main && !item.main.id))
return false;
if (item.drilldowns) {
const invalidDrilldowns = item.drilldowns.filter(drilldown => {
const invalidDrilldownElements =
drilldown.elements &&
drilldown.elements.filter(element => {
return !element.id;
});
return (
!drilldown.id &&
!drilldown.elements &&
invalidDrilldownElements.length !== 0
);
});
return invalidDrilldowns.length === 0;
}
return true;
};
I want to create a custom Joi type for populatedStrings by using .extend(..) to create a type based on joi.string() which:
Trims the string
Changes the value to undefined if the trimmed string === '' so the validated output won't contain the key at all
Overrides .required() so it acts on the trimmed string and creates errors using my own language. When .required() is set on my type it means that it requires a string which does not only contain white space or is empty
My attempt so far which is close:
const StandardJoi = require("joi");
const Joi = StandardJoi.extend(joi => ({
base: joi.string(),
name: "populatedString",
language: {
required: "needs to be a a string containing non whitespace characters"
},
pre(value, state, options) {
value = value.trim();
return value === "" ? undefined : value;
},
rules: [
{
name: "required",
validate(params, value, state, options) {
if (value === undefined) {
return this.createError(
"populatedString.required",
{ v: value },
state,
options
);
}
return value;
}
}
]
}));
Examples of it working
Joi.populatedString().validate(" x "); // $.value === 'x'
Joi.populatedString().validate(" "); // $.value === undefined
// $.error.details
//
// [ { message: '"value" needs to be a a string containing non whitespace characters',
// path: [],
// type: 'populatedString.required',
// context: { v: undefined, key: undefined, label: 'value' } } ]
Joi.populatedString()
.required()
.validate(" ");
// $.value
//
// { inObj1: 'o' }
Joi.object()
.keys({
inObj1: Joi.populatedString()
})
.validate({ inObj1: " o " });
But it does not fail (as it should) for
// { error: null, value: {}, then: [λ: then], catch: [λ: catch] }
Joi.object()
.keys({
inObj2: Joi.populatedString(),
inObj3: Joi.populatedString().required()
})
.validate({ inObj2: " " });
Even though inObj3 is .required() and not supplied it doesn't fail.
I managed to solve it:
const BaseJoi = require("joi");
const Joi = BaseJoi.extend(joi => ({
base: joi.string(),
name: "populatedString",
language: {
required: "needs to be a a string containing non whitespace characters"
},
pre(value, state, options) {
value = value.trim();
return value === "" ? undefined : value;
},
rules: [
{
name: "required",
setup(params) {
return this.options({ presence: "required" });
},
validate(params, value, state, options) {
if (value === undefined) {
return this.createError(
"populatedString.required",
{ v: value },
state,
options
);
}
return value;
}
}
]
}));
The fix was to add setup and let it set the option presence = required if required() was called.