@atproto/lexicon
Version:
atproto Lexicon schema language library
312 lines • 10.7 kB
JavaScript
import { CID } from 'multiformats/cid';
import { graphemeLen, utf8Len } from '@atproto/common-web';
import { ValidationError } from '../types.js';
import * as formats from './formats.js';
export function validate(lexicons, path, def, value) {
switch (def.type) {
case 'boolean':
return boolean(lexicons, path, def, value);
case 'integer':
return integer(lexicons, path, def, value);
case 'string':
return string(lexicons, path, def, value);
case 'bytes':
return bytes(lexicons, path, def, value);
case 'cid-link':
return cidLink(lexicons, path, def, value);
case 'unknown':
return unknown(lexicons, path, def, value);
default:
return {
success: false,
error: new ValidationError(`Unexpected lexicon type: ${def.type}`),
};
}
}
function boolean(lexicons, path, def, value) {
def = def;
// type
const type = typeof value;
if (type === 'undefined') {
if (typeof def.default === 'boolean') {
return { success: true, value: def.default };
}
return {
success: false,
error: new ValidationError(`${path} must be a boolean`),
};
}
else if (type !== 'boolean') {
return {
success: false,
error: new ValidationError(`${path} must be a boolean`),
};
}
// const
if (typeof def.const === 'boolean') {
if (value !== def.const) {
return {
success: false,
error: new ValidationError(`${path} must be ${def.const}`),
};
}
}
return { success: true, value };
}
function integer(lexicons, path, def, value) {
def = def;
// type
const type = typeof value;
if (type === 'undefined') {
if (typeof def.default === 'number') {
return { success: true, value: def.default };
}
return {
success: false,
error: new ValidationError(`${path} must be an integer`),
};
}
else if (!Number.isInteger(value)) {
return {
success: false,
error: new ValidationError(`${path} must be an integer`),
};
}
// const
if (typeof def.const === 'number') {
if (value !== def.const) {
return {
success: false,
error: new ValidationError(`${path} must be ${def.const}`),
};
}
}
// enum
if (Array.isArray(def.enum)) {
if (!def.enum.includes(value)) {
return {
success: false,
error: new ValidationError(`${path} must be one of (${def.enum.join('|')})`),
};
}
}
// maximum
if (typeof def.maximum === 'number') {
if (value > def.maximum) {
return {
success: false,
error: new ValidationError(`${path} can not be greater than ${def.maximum}`),
};
}
}
// minimum
if (typeof def.minimum === 'number') {
if (value < def.minimum) {
return {
success: false,
error: new ValidationError(`${path} can not be less than ${def.minimum}`),
};
}
}
return { success: true, value };
}
function string(lexicons, path, def, value) {
def = def;
// type
if (typeof value === 'undefined') {
if (typeof def.default === 'string') {
return { success: true, value: def.default };
}
return {
success: false,
error: new ValidationError(`${path} must be a string`),
};
}
else if (typeof value !== 'string') {
return {
success: false,
error: new ValidationError(`${path} must be a string`),
};
}
// const
if (typeof def.const === 'string') {
if (value !== def.const) {
return {
success: false,
error: new ValidationError(`${path} must be ${def.const}`),
};
}
}
// enum
if (Array.isArray(def.enum)) {
if (!def.enum.includes(value)) {
return {
success: false,
error: new ValidationError(`${path} must be one of (${def.enum.join('|')})`),
};
}
}
// maxLength and minLength
if (typeof def.minLength === 'number' || typeof def.maxLength === 'number') {
// If the JavaScript string length * 3 is below the maximum limit,
// its UTF8 length (which <= .length * 3) will also be below.
if (typeof def.minLength === 'number' && value.length * 3 < def.minLength) {
return {
success: false,
error: new ValidationError(`${path} must not be shorter than ${def.minLength} characters`),
};
}
// If the JavaScript string length * 3 is within the maximum limit,
// its UTF8 length (which <= .length * 3) will also be within.
// When there's no minimal length, this lets us skip the UTF8 length check.
let canSkipUtf8LenChecks = false;
if (typeof def.minLength === 'undefined' &&
typeof def.maxLength === 'number' &&
value.length * 3 <= def.maxLength) {
canSkipUtf8LenChecks = true;
}
if (!canSkipUtf8LenChecks) {
const len = utf8Len(value);
if (typeof def.maxLength === 'number') {
if (len > def.maxLength) {
return {
success: false,
error: new ValidationError(`${path} must not be longer than ${def.maxLength} characters`),
};
}
}
if (typeof def.minLength === 'number') {
if (len < def.minLength) {
return {
success: false,
error: new ValidationError(`${path} must not be shorter than ${def.minLength} characters`),
};
}
}
}
}
// maxGraphemes and minGraphemes
if (typeof def.maxGraphemes === 'number' ||
typeof def.minGraphemes === 'number') {
let needsMaxGraphemesCheck = false;
let needsMinGraphemesCheck = false;
if (typeof def.maxGraphemes === 'number') {
if (value.length <= def.maxGraphemes) {
// If the JavaScript string length (UTF-16) is within the maximum limit,
// its grapheme length (which <= .length) will also be within.
needsMaxGraphemesCheck = false;
}
else {
needsMaxGraphemesCheck = true;
}
}
if (typeof def.minGraphemes === 'number') {
if (value.length < def.minGraphemes) {
// If the JavaScript string length (UTF-16) is below the minimal limit,
// its grapheme length (which <= .length) will also be below.
// Fail early.
return {
success: false,
error: new ValidationError(`${path} must not be shorter than ${def.minGraphemes} graphemes`),
};
}
else {
needsMinGraphemesCheck = true;
}
}
if (needsMaxGraphemesCheck || needsMinGraphemesCheck) {
const len = graphemeLen(value);
if (typeof def.maxGraphemes === 'number') {
if (len > def.maxGraphemes) {
return {
success: false,
error: new ValidationError(`${path} must not be longer than ${def.maxGraphemes} graphemes`),
};
}
}
if (typeof def.minGraphemes === 'number') {
if (len < def.minGraphemes) {
return {
success: false,
error: new ValidationError(`${path} must not be shorter than ${def.minGraphemes} graphemes`),
};
}
}
}
}
if (typeof def.format === 'string') {
switch (def.format) {
case 'datetime':
return formats.datetime(path, value);
case 'uri':
return formats.uri(path, value);
case 'at-uri':
return formats.atUri(path, value);
case 'did':
return formats.did(path, value);
case 'handle':
return formats.handle(path, value);
case 'at-identifier':
return formats.atIdentifier(path, value);
case 'nsid':
return formats.nsid(path, value);
case 'cid':
return formats.cid(path, value);
case 'language':
return formats.language(path, value);
case 'tid':
return formats.tid(path, value);
case 'record-key':
return formats.recordKey(path, value);
}
}
return { success: true, value };
}
function bytes(lexicons, path, def, value) {
def = def;
if (!value || !(value instanceof Uint8Array)) {
return {
success: false,
error: new ValidationError(`${path} must be a byte array`),
};
}
// maxLength
if (typeof def.maxLength === 'number') {
if (value.byteLength > def.maxLength) {
return {
success: false,
error: new ValidationError(`${path} must not be larger than ${def.maxLength} bytes`),
};
}
}
// minLength
if (typeof def.minLength === 'number') {
if (value.byteLength < def.minLength) {
return {
success: false,
error: new ValidationError(`${path} must not be smaller than ${def.minLength} bytes`),
};
}
}
return { success: true, value };
}
function cidLink(lexicons, path, def, value) {
if (CID.asCID(value) === null) {
return {
success: false,
error: new ValidationError(`${path} must be a CID`),
};
}
return { success: true, value };
}
function unknown(lexicons, path, def, value) {
// type
if (!value || typeof value !== 'object') {
return {
success: false,
error: new ValidationError(`${path} must be an object`),
};
}
return { success: true, value };
}
//# sourceMappingURL=primitives.js.map