UNPKG

@platform/cell.schema

Version:

URI and database schemas for the `cell.os`.

167 lines (166 loc) 6.55 kB
import { cuid, slug, coord, wildcard } from '../common'; const ALLOW = { NS: [] }; export const DEFAULT = { ALLOW }; export class Uri { static parse(input) { let text = (input || '').trim().split('?')[0]; let data = { type: 'UNKNOWN' }; let error; const toString = () => text; const setError = (isError, message) => { if (!error && isError) { error = { type: 'URI', message, uri: text }; } }; setError(!text, 'URI not specified'); const index = text.indexOf(':'); setError(index < 0, 'Not a valid multi-part URI'); if (!error) { const left = text.substring(0, index); const right = text.substring(index + 1).trim(); if (left === 'ns') { const id = right; setError(!id, 'Namespace URI identifier not found'); setError(!Uri.is.valid.ns(id), `URI contains an invalid "ns" identifier ("${id}")`); const uri = { type: 'NS', id, toString }; data = uri; } else if (left === 'file') { const id = right; setError(!id, 'File URI identifier not found'); const parts = id.split(':'); const ns = (parts[0] || '').trim(); const file = (parts[1] || '').trim(); setError(!file, `File identifier within namespace "${ns}" not found`); const uri = { type: 'FILE', id, ns, file, toString }; data = uri; } else if (left === 'cell') { const id = right || ''; setError(!id, `ID of 'cell' not found`); let type = 'CELL'; let key = ''; let ns = ''; if (!id.includes(':')) { setError(true, `The 'cell' URI does not have a coordinate address, eg. ":A1" in "cell:foo:A1"`); } else { const bang = id.replace(/\:/g, '!'); const parts = coord.cell.toCell(bang); type = coord.cell.toType(bang); key = parts.key; ns = parts.ns; setError(!key, `Coordinate key of '${type || '<empty>'}' not found`); setError(!ns, `Coordinate namespace of '${type || '<empty>'}' not found`); if (!error) { text = toUri('cell', type, ns, key); } } data = { type, id, ns, key, toString }; } } const ok = !Boolean(error) && data.type !== 'UNKNOWN'; const res = { ok, uri: text, type: data.type, parts: data, toString, }; return error ? Object.assign(Object.assign({}, res), { error }) : res; } } Uri.cuid = cuid; Uri.slug = slug; Uri.ALLOW = Object.assign({}, DEFAULT.ALLOW); Uri.create = { ns: (id) => toUri('ns', 'NS', id), cell: (ns, key) => toUri('cell', 'CELL', ns, key), row: (ns, key) => toUri('cell', 'ROW', ns, key), column: (ns, key) => toUri('cell', 'COLUMN', ns, key), file: (ns, fileid) => toUri('file', 'FILE', ns, fileid), }; Uri.is = { uri: (input) => Uri.parse(input).ok, type: (type, input) => { const uri = Uri.parse(input); return uri.parts.type === type && (type === 'UNKNOWN' ? true : uri.ok); }, ns: (input) => Uri.is.type('NS', input), file: (input) => Uri.is.type('FILE', input), cell: (input) => Uri.is.type('CELL', input), row: (input) => Uri.is.type('ROW', input), column: (input) => Uri.is.type('COLUMN', input), valid: { ns(input) { const value = (input || '').replace(/^ns\:/, ''); if (!value) { return false; } if (isCuid(value)) { return true; } const matchLegal = value.match(/^[A-Za-z0-9\.]*$/); if (!matchLegal || (matchLegal && matchLegal[0] !== value)) { return false; } return Uri.ALLOW.NS.some(pattern => { return typeof pattern === 'string' ? pattern.includes('*') ? wildcard.isMatch(value, pattern) : pattern === value : pattern(value); }); }, }, }; const alphaNumeric = new RegExp(/^[a-z0-9]+$/i); function trimPrefix(prefix, input) { const regex = new RegExp(`^${prefix}\:+`); return input.trim().replace(regex, ''); } function toUri(prefix, type, id, suffix) { id = (id || '').trim(); id = id === ':' ? '' : id; if (id) { ['ns', 'cell', 'file'].forEach(prefix => (id = trimPrefix(prefix, id))); } if (!id) { throw new Error(`The "${prefix}" URI was not supplied with a namespace identifier. ("${id}")`); } if (!Uri.is.valid.ns(id)) { const err = `URI contains an invalid "${prefix}" identifier, must be an alpha-numeric cuid. ("${id}")`; throw new Error(err); } if (typeof suffix === 'string') { suffix = (suffix || '').trim().replace(/^\:*/, ''); if (!suffix) { throw new Error(`The "${prefix}" URI was not supplied with a suffix key.`); } if (prefix === 'file') { if (!alphaNumeric.test(suffix)) { const err = `The "file" URI contains an invalid file-identifier, must be alpha-numeric ("${suffix}").`; throw new Error(err); } suffix = `:${suffix}`; } else { if (suffix === 'undefined') { const err = `The suffix "undefined" (string) was given - this it likely an upstream error where an [undefined] value has been converted into a string.`; throw new Error(err); } suffix = suffix || ''; const suffixType = coord.cell.toType(suffix) || ''; if (suffixType !== type) { const key = suffix || ''; const err = `The "${prefix}:" URI was not supplied with a valid ${type} key (given key "${key}").`; throw new Error(err); } suffix = `:${suffix}`; } } return `${prefix}:${id}${suffix || ''}`; } function isCuid(input) { return input.length === 25 && input[0] === 'c' && alphaNumeric.test(input); }