@unique-nft/api
Version:
Definitely typed JS API for the Unique Network blockchain
221 lines (174 loc) • 8 kB
text/typescript
import {ValidationError} from "../../../utils/errors";
import {TokenPropertyPermissionObject} from "../../../substrate/extrinsics/unique/types";
import {
BoxedNumberWithDefault,
InfixOrUrlOrCidAndHash,
LocalizedStringWithDefault,
URL_TEMPLATE_INFIX,
UrlTemplateString
} from "../../types";
import {getKeys} from "../../../tsUtils";
import {Semver} from "../../../utils/semver";
import {LANG_REGEX} from "./constants";
export const isPlainObject = (obj: any, varName: string): obj is Object => {
if (typeof obj !== 'object')
throw new ValidationError(`${varName} is not an object, got ${typeof obj}: ${obj}`)
if (obj === null)
throw new ValidationError(`${varName} is a null, should be valid object`)
if (obj instanceof Map)
throw new ValidationError(`${varName} is a Map, should be plain object`)
if (obj instanceof Set)
throw new ValidationError(`${varName} is a Set, should be plain object`)
if (Array.isArray(obj))
throw new ValidationError(`${varName} is an array, should be plain object`)
return true
}
export const validateNumber = (num: any, shouldBeInteger: boolean, varName: string): num is number => {
if (typeof num !== 'number' || isNaN(num)) {
throw new ValidationError(`${varName} is not a valid number, got ${num}`)
}
if (shouldBeInteger && num !== Math.round(num)) {
throw new ValidationError(`${varName} is not an integer number, got ${num}`)
}
return true
}
export const validateAttributeKey = (num: string | number | symbol, varName: string): boolean => {
let isOk = false
if (typeof num === 'number') {
isOk = num === Math.round(num);
} else if (typeof num === 'string') {
const parsed = parseFloat(num);
isOk = !isNaN(parsed) && parsed === Math.round(parsed);
}
if (!isOk) {
throw new ValidationError(`${varName}["${String(num)}"] is not a valid number key, got ${String(num)}`)
}
return true
}
const validateLangCode = (key: string | number | symbol, varName: string): boolean => {
if (typeof key !== 'string') {
throw new ValidationError(`${varName}: key ${String(key)} should be a string`)
}
if (!key.match(LANG_REGEX)) {
throw new ValidationError(`${varName} should be a valid Language code string (like 'co' or 'ca-ES'), got ${key}`)
}
return true
}
export const validateURL = (url: string, varName: string): boolean => {
if (typeof url !== 'string') {
throw new ValidationError(`${varName} should be a string`)
}
try {
new URL(url)
return true
} catch (err) {
throw new ValidationError(`${varName} should be a valid URL, got ${url}`)
}
}
export const validateAndParseSemverString = (str: string, varName: string): Semver => {
if (!Semver.isValid(str))
throw new ValidationError(`${varName} is not a valid semver string (passed ${str})`)
return Semver.fromString(str)
}
export const validateLocalizedStringWithDefault = (dict: any, canHaveLocalization: boolean, varName: string): dict is LocalizedStringWithDefault => {
isPlainObject(dict, varName)
const keys = getKeys(dict)
if (keys.length === 0) {
throw new ValidationError(`${varName} is an empty object, should have at least one key`)
}
if (!dict.hasOwnProperty('_')) {
throw new ValidationError(`${varName} is doesn't contain field "_"`)
}
if (typeof dict._ !== 'string') {
throw new ValidationError(`${varName}._ is not a string`)
}
if (!canHaveLocalization && keys.length !== 1) {
throw new ValidationError(`${varName} cannot have localization strings, got object with keys ["${keys.join('", "')}"]`)
}
for (const key in dict) {
if (key === '_') continue
validateLangCode(key, `${varName}["${key}"]`)
if (typeof dict[key] !== 'string') {
throw new ValidationError(`${varName}["${key}"] should be a string, got ${typeof key}: ${key}`)
}
}
return true
}
export const validateBoxedNumberWithDefault = (dict: BoxedNumberWithDefault, shouldBeInteger: boolean, varName: string): dict is BoxedNumberWithDefault => {
isPlainObject(dict, varName)
if (getKeys(dict).length === 0) {
throw new ValidationError(`${varName} is an empty object, should have at least one key`)
}
if (!dict.hasOwnProperty('_')) {
throw new ValidationError(`${varName} is doesn't contain field "_"`)
}
validateNumber(dict._, shouldBeInteger, `${varName}._`)
for (const key in dict) {
if (key === '_') continue
}
return true
}
export const validateUrlTemplateString = (str: any, varName: string): str is UrlTemplateString => {
const prefix = `TemplateUrlString is not valid, ${varName}`
if (typeof str !== 'string')
throw new ValidationError(`${prefix} is not a string, got ${str}`)
if (str.indexOf(URL_TEMPLATE_INFIX) < 0)
throw new ValidationError(`${prefix} doesn't contain "${URL_TEMPLATE_INFIX}", got ${str}`)
return true
}
export const validateUrlWithHashObject = (obj: any, varName: string): obj is InfixOrUrlOrCidAndHash => {
isPlainObject(obj, varName)
const keysAmount = ['urlInfix', 'url', 'ipfsCid']
.map(field => Number(typeof obj[field] === 'string'))
.reduce((prev, curr) => {
return prev + curr
}, 0)
if (keysAmount !== 1) {
throw new ValidationError(`${varName} should have one and only one of "urlInfix" or "url" or "ipfsCid" string fields, got ${JSON.stringify(obj)}`)
}
if (typeof obj.url === 'string') {
validateURL(obj.url, `${varName}.url`)
}
if (obj.hasOwnProperty('hash'))
validateFieldByType(obj, 'hash', 'string', false, varName)
return true
}
export const validateFieldByType = <T extends object>(obj: T, key: keyof T, type: string, optional: boolean, varName: string): boolean => {
isPlainObject(obj, varName)
if (optional) {
if (obj.hasOwnProperty(key) && typeof obj[key] !== type) {
throw new ValidationError(`${varName}.${String(key)} is passed and not a ${type}, got ${typeof obj[key]}: ${obj[key]}`)
}
} else {
if (!obj.hasOwnProperty(key)) {
throw new ValidationError(`${varName}.${String(key)} not found in ${varName}`)
}
if (typeof obj[key] !== type) {
throw new ValidationError(`${varName}.${String(key)} should be a ${type}, got ${typeof obj[key]}: ${obj[key]}`)
}
}
return true
}
export const validateSingleTokenPropertyPermission = (tpp: any, varName: string): tpp is TokenPropertyPermissionObject => {
isPlainObject(tpp, varName)
validateFieldByType(tpp, 'key', 'string', false, varName)
const permissionVarName = `${varName}.permission`
isPlainObject(tpp.permission, permissionVarName)
validateFieldByType(tpp.permission, 'mutable', 'boolean', false, permissionVarName)
validateFieldByType(tpp.permission, 'collectionAdmin', 'boolean', false, permissionVarName)
validateFieldByType(tpp.permission, 'tokenOwner', 'boolean', false, permissionVarName)
return true
}
export const checkSafeFactory = <T extends (...args: any) => any>(fn: T) => {
const returnFn = (...params: Parameters<T>) => {
try {
return fn(...params as any)
} catch {
return false as ReturnType<T>
}
}
return returnFn as T
}
export const validateUrlTemplateStringSafe = checkSafeFactory(validateUrlTemplateString)
export const validateURLSafe = checkSafeFactory(validateURL)
export const validateLocalizedStringWithDefaultSafe = checkSafeFactory(validateLocalizedStringWithDefault)