apxtrib/nationchains/socialworld/contracts/checkdata.js
2023-03-27 07:52:21 +02:00

302 lines
10 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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: /^([09]{1,3}.){3}.([09]{1,3})$/,
ipv6: /^((([09A-Fa-f]{1,4}:){7}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){6}:[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){5}:([09A-Fa-f]{1,4}:)?[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){4}:([09A-Fa-f]{1,4}:){0,2}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){3}:([09A-Fa-f]{1,4}:){0,3}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){2}:([09A-Fa-f]{1,4}:){0,4}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){6}((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|(([09A-Fa-f]{1,4}:){0,5}:((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|(::([09A-Fa-f]{1,4}:){0,5}((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|([09A-Fa-f]{1,4}::([09A-Fa-f]{1,4}:){0,5}[09A-Fa-f]{1,4})|(::([09A-Fa-f]{1,4}:){0,6}[09A-Fa-f]{1,4})|(([09A-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;