forked from apxtri/apxtrib
302 lines
10 KiB
JavaScript
Executable File
302 lines
10 KiB
JavaScript
Executable File
/*
|
||
This module have to be independant of any external package
|
||
it is shared between back and front and is usefull
|
||
to apply common check in front before sending it in back
|
||
can be include in project with
|
||
- into a browser : <script src="https://townName.nationName.dns/socialworld/contracts/check.js"></script>
|
||
- into a node.js : const check = require( `../nationchains/socialworld/contracts/check.js`);
|
||
*/
|
||
// --##
|
||
|
||
const check = {};
|
||
check.schema = {};
|
||
check.schema.properties = {};
|
||
check.schema.properties.type = {};
|
||
check.schema.properties.type.string = (str) => typeof str === "string";
|
||
check.schema.properties.type.number = (n) => typeof n === "number";
|
||
check.schema.properties.type.integer = (n) =>
|
||
n != "" && !isNaN(n) && Math.round(n) == n;
|
||
check.schema.properties.type.float = (n) =>
|
||
n != "" && !isNaN(n) && Math.round(n) != n; //not yet in json schema
|
||
check.schema.properties.minLength = (str, min) =>
|
||
typeof str === "string" && str.length > parseInt(min);
|
||
check.schema.properties.maxLength = (str, max) =>
|
||
typeof str === "string" && str.length < parseInt(max);
|
||
check.schema.properties.multipleOf = (n, val) =>
|
||
typeof n === "number" &&
|
||
typeof val === "number" &&
|
||
parseFloat(n) / parseFloat(val) -
|
||
Math.round(parseFloat(n) / parseFloat(val)) <
|
||
0.0000001;
|
||
check.schema.properties.range = (
|
||
n,
|
||
minimum,
|
||
exclusiveMinimum,
|
||
maximum,
|
||
exclusiveMaximum
|
||
) => {
|
||
//console.log(minimum,exclusiveMinimum,maximum, exclusiveMaximum,n)
|
||
if (typeof n !== "number") return false;
|
||
if (minimum && parseFloat(n) < parseFloat(minimum)) return false;
|
||
if (exclusiveMinimum && parseFloat(n) <= parseFloat(exclusiveMinimum))
|
||
return false;
|
||
if (maximum && parseFloat(n) > parseFloat(maximum)) return false;
|
||
if (exclusiveMaximum && parseFloat(n) >= parseFloat(exclusiveMaximum))
|
||
return false;
|
||
return true;
|
||
};
|
||
check.schema.properties.pattern = (str, pattern) => {
|
||
try {
|
||
new RegExp(pattern);
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
return pattern.test(str);
|
||
};
|
||
check.schema.properties.enum = (str, enumvalues) =>
|
||
typeof str === "string" && enumvalues.includes(str);
|
||
// see format https://json-schema.org/understanding-json-schema/reference/string.html#format
|
||
check.schema.properties.format = {
|
||
"date-time": / /,
|
||
time: / /,
|
||
date: / /,
|
||
duration: / /,
|
||
email:
|
||
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||
"idn-email": / /,
|
||
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
|
||
uri: / /,
|
||
"uri-reference": / /,
|
||
iri: / /,
|
||
hostname: / /,
|
||
"idn-hostname": / /,
|
||
ipv4: /^([0–9]{1,3}.){3}.([0–9]{1,3})$/,
|
||
ipv6: /^((([0–9A-Fa-f]{1,4}:){7}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}:[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){5}:([0–9A-Fa-f]{1,4}:)?[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){4}:([0–9A-Fa-f]{1,4}:){0,2}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){3}:([0–9A-Fa-f]{1,4}:){0,3}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){2}:([0–9A-Fa-f]{1,4}:){0,4}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(([0–9A-Fa-f]{1,4}:){0,5}:((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(::([0–9A-Fa-f]{1,4}:){0,5}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|([0–9A-Fa-f]{1,4}::([0–9A-Fa-f]{1,4}:){0,5}[0–9A-Fa-f]{1,4})|(::([0–9A-Fa-f]{1,4}:){0,6}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){1,7}:))$/,
|
||
telephonefr: /^0[1-9][0-9]{9}$/,
|
||
telephoneinter: /^\+*(\d{3})*[0-9,\-]{8,}/,
|
||
password:
|
||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/,
|
||
postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/,
|
||
};
|
||
|
||
check.schema.validation = (schema) => {
|
||
/*validate a schema structure*/
|
||
const res = { status: 200, err: [] };
|
||
if (schema.properties) {
|
||
Object.keys(schema.properties).forEach((p) => {
|
||
const properties = schema.properties;
|
||
if (
|
||
properties[p].type &&
|
||
typeof properties[p].type === "string" &&
|
||
!check.schema.properties.type[properties[p].type]
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|typedoesnotexistinschema",
|
||
moreinfo: ` ${properties[p].type}`,
|
||
});
|
||
}
|
||
if (
|
||
properties[p].type &&
|
||
typeof properties[p].type === "object" &&
|
||
Array.isArray(properties[p].type)
|
||
) {
|
||
properties[p].type.forEach((tp) => {
|
||
if (!check.schema.properties.type[tp])
|
||
res.err.push({
|
||
info: "|checkdata|typedoesnotexistinschema",
|
||
moreinfo: `${tp} of ${properties[p].type}`,
|
||
});
|
||
});
|
||
}
|
||
if (
|
||
properties[p].format &&
|
||
!check.schema.properties.format[properties[p].format]
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|formatdoesnotexistinschema",
|
||
moreinfo: ` ${properties[p].format}`,
|
||
});
|
||
}
|
||
if (properties[p].enum && !Array.isArray(properties[p].enum)) {
|
||
res.err.push({
|
||
info: "|checkdata|enumisnotarrayinschema",
|
||
moreinfo: ` ${properties[p].enum}`,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
// 406 means not acceptable
|
||
if (res.err.length > 0) res.status = 406;
|
||
return res;
|
||
};
|
||
|
||
check.schema.data = (schema, ctx, data) => {
|
||
/* validate a data set with a schema in a context ctx */
|
||
/*
|
||
console.log('#################')
|
||
console.log(schema);
|
||
console.log('---------')
|
||
console.log(data)
|
||
*/
|
||
const validschema = check.schema.validation(schema);
|
||
if (validschema.status != 200) return validschema;
|
||
const res = { status: 200, err: [] };
|
||
if (schema.properties) {
|
||
const properties = schema.properties;
|
||
Object.keys(properties).forEach((p) => {
|
||
//type is mandatory in a propertie
|
||
if (data[p]) {
|
||
const typlist =
|
||
properties[p].type && typeof properties[p].type === "string"
|
||
? [properties[p].type]
|
||
: properties[p].type;
|
||
let valid = false;
|
||
typlist.forEach((typ) => {
|
||
// at least one test have to be ok
|
||
if (check.schema.properties.type[typ](data[p])) valid = true;
|
||
});
|
||
if (!valid)
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]}`,
|
||
});
|
||
|
||
if (
|
||
properties[p].minLength &&
|
||
!check.schema.properties.minLength(data[p], properties[p].minLength)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} minLength:${properties[p].minLength}`,
|
||
});
|
||
}
|
||
if (
|
||
properties[p].maxLength &&
|
||
!check.schema.properties.maxLength(data[p], properties[p].maxLength)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} maxLength:${properties[p].maxLength}`,
|
||
});
|
||
}
|
||
if (
|
||
properties[p].multipleOf &&
|
||
!check.schema.properties.multipleOf(data[p], properties[p].multipleOf)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} not a multipleOf:${properties[p].multipleOf}`,
|
||
});
|
||
}
|
||
if (
|
||
properties[p].minimum ||
|
||
properties[p].maximum ||
|
||
properties[p].exclusiveMinimum ||
|
||
properties[p].exclusiveMaximum
|
||
) {
|
||
// test range
|
||
if (
|
||
!check.schema.properties.range(
|
||
data[p],
|
||
properties[p].minimum,
|
||
properties[p].exclusiveMinimum,
|
||
properties[p].maximum,
|
||
properties[p].exclusiveMaximum
|
||
)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} not in range ${properties[p].minimum} exclu: ${properties[p].exclusiveMinimum} and ${properties[p].maximum} exclu: ${properties[p].exclusiveMaximum}`,
|
||
});
|
||
}
|
||
}
|
||
if (
|
||
properties[p].enum &&
|
||
!check.schema.properties.enum(data[p], properties[p].enum)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} not in enum list :${properties[p].enum}`,
|
||
});
|
||
}
|
||
if (properties[p].format) {
|
||
properties[p].pattern =
|
||
check.schema.properties.format[properties[p].format];
|
||
}
|
||
if (
|
||
properties[p].pattern &&
|
||
!check.schema.properties.pattern(data[p], properties[p].pattern)
|
||
) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertie",
|
||
moreinfo: `${p} : ${data[p]} problem pattern or format ${properties[p].pattern}`,
|
||
});
|
||
}
|
||
} else if (schema.required.includes(p)) {
|
||
res.err.push({
|
||
info: "|checkdata|dataerrpropertiesrequired",
|
||
moreinfo: `${p}`,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
if (res.err.length > 0) res.status = 417;
|
||
return res;
|
||
};
|
||
|
||
/*
|
||
Normalize data link to check.schema.properties.format
|
||
or any normalization to get consistent data
|
||
*/
|
||
const normalize={};
|
||
normalize.telephonefr =(phone)=>{
|
||
phone = phone.trim().replace(/[- .]/g, "");
|
||
if (
|
||
check.schema.properties.format.telephoenfr(phone) &&
|
||
phone.length == 10 &&
|
||
phone[0] == "0"
|
||
) {
|
||
phone = "+33 " + phone.substring(1);
|
||
}
|
||
return phone;
|
||
};
|
||
normalize.zfill10 = (num) => {
|
||
let s = num + "";
|
||
while (s.length < 10) s = "0" + s;
|
||
return s;
|
||
};
|
||
|
||
|
||
check.test.unique = (ctx, val) => {
|
||
if (ctx.list[ctx.currentfield]) {
|
||
return !ctx.list[ctx.currentfield].includes(val);
|
||
} else {
|
||
console.log("ERR no list for field:" + ctx.currentfield);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// check.normalize take a correct data then reformat it to harmonise it
|
||
check.normalize = {};
|
||
check.normalize.phoneNumber = (ctx, phone) => {
|
||
phone = phone.trim().replace(/[- .]/g, "");
|
||
if (
|
||
check.test.phoneNumber("", phone) &&
|
||
phone.length == 10 &&
|
||
phone[0] == "0"
|
||
) {
|
||
phone = "+33 " + phone.substring(1);
|
||
}
|
||
return phone;
|
||
};
|
||
check.normalize.upperCase = (ctx, txt) => txt.toUpperCase();
|
||
check.normalize.lowerCase = (ctx, txt) => txt.toLowerCase();
|
||
// fixe 10 position et complete par des 0 devant
|
||
check.normalize.zfill10 = (ctx, num) => {
|
||
let s = num + "";
|
||
while (s.length < 10) s = "0" + s;
|
||
return s;
|
||
};
|
||
|
||
if (typeof module !== "undefined") module.exports = check;
|