UNPKG

scrivito

Version:

Scrivito is a professional, yet easy to use SaaS Enterprise Content Management Service, built for digital agencies and medium to large businesses. It is completely maintenance-free, cost-effective, and has unprecedented performance and security.

424 lines (353 loc) 12.1 kB
import isDate from 'lodash-es/isDate'; import isEmpty from 'lodash-es/isEmpty'; import { AttributeJson, LinkJson } from 'scrivito_sdk/client'; import { ArgumentError, formatDateToString, isCamelCase, isEmptyValue, isObject, isStringArray, isSystemAttribute, isValidDateString, isValidFloat, isValidInteger, prettyPrint, underscore, } from 'scrivito_sdk/common'; import { ObjJsonPatch } from 'scrivito_sdk/data'; import { NormalizedBasicAttributesWithUnknownValues } from 'scrivito_sdk/models/basic_attribute_content'; import { CmsAttributeType } from 'scrivito_sdk/models/basic_attribute_types'; import { BasicLink } from 'scrivito_sdk/models/basic_link'; import { BasicObj } from 'scrivito_sdk/models/basic_obj'; import { BasicWidget } from 'scrivito_sdk/models/basic_widget'; import { Binary } from 'scrivito_sdk/models/binary'; import { DataLocator } from 'scrivito_sdk/models/data_locator'; import { isObjId } from 'scrivito_sdk/models/is_obj_id'; import { ObjUnavailable } from 'scrivito_sdk/models/obj_unavailable'; import { BasicTypeInfo } from 'scrivito_sdk/models/type_info'; type ValidLinkInputValue = BasicLink | PlainLinkObject; interface PlainLinkObject { obj_id?: string | null; query?: string | null; rel?: string | null; target?: string | null; title?: string | null; url?: string | null; hash?: string | null; // corresponds to the key "fragment" of LinkJson } export function serialize( attributes: NormalizedBasicAttributesWithUnknownValues, ): ObjJsonPatch { const serializedAttributes: ObjJsonPatch = {}; Object.keys(attributes).forEach((name) => { const serializedName = convertCamelCasedAttributeName(name); if (isSystemAttribute(serializedName)) { const [value] = attributes[name] as [ObjJsonPatch[keyof ObjJsonPatch]]; serializedAttributes[serializedName] = value; } else { const [value, typeInfo] = attributes[name]; if (value !== undefined) { serializedAttributes[serializedName] = serializeAttribute( value, name, typeInfo!, ); } } }); return serializedAttributes; } export function serializeAttribute<Type extends CmsAttributeType>( value: unknown, name: string, typeInfo: BasicTypeInfo<Type>, ): AttributeJson | null { if (value === null) return null; const serializedEntry = serializeEntry(value, name, typeInfo); if (isEmptyValue(serializedEntry[1])) return null; return serializedEntry; } function serializeEntry<Type extends CmsAttributeType>( value: unknown, name: string, typeInfo: BasicTypeInfo<Type>, ): AttributeJson { switch (typeInfo[0]) { case 'binary': return ['binary', serializeBinaryAttributeValue(value, name)]; case 'boolean': return ['boolean', serializeBooleanAttributeValue(value, name)]; case 'datalocator': return ['datalocator', serializeDataLocatorAttributeValue(value, name)]; case 'date': return ['date', serializeDateAttributeValue(value, name)]; case 'datetime': return ['date', serializeDateAttributeValue(value, name)]; case 'enum': return ['string', serializeEnumAttributeValue(value, name, typeInfo[1])]; case 'float': return ['number', serializeFloatAttributeValue(value, name)]; case 'html': return ['html', serializeHtmlAttributeValue(value, name)]; case 'integer': return ['number', serializeIntegerAttributeValue(value, name)]; case 'link': return ['link', serializeLinkAttributeValue(value, name)]; case 'linklist': return ['linklist', serializeLinklistAttributeValue(value, name)]; case 'multienum': return [ 'stringlist', serializeMultienumAttributeValue(value, name, typeInfo[1]), ]; case 'reference': return ['reference', serializeReferenceAttributeValue(value, name)]; case 'referencelist': return [ 'referencelist', serializeReferencelistAttributeValue(value, name), ]; case 'string': return ['string', serializeStringAttributeValue(value, name)]; case 'stringlist': return ['stringlist', serializeStringlistAttributeValue(value, name)]; case 'widget': return ['widget', serializeWidgetAttributeValue(value, name)]; case 'widgetlist': return ['widgetlist', serializeWidgetlistAttributeValue(value, name)]; default: throw new ArgumentError( `Attribute "${name}" is of unsupported type "${typeInfo[0]}".`, ); } } function throwInvalidAttributeValue( value: unknown, name: string, expected: string, ): never { throw new ArgumentError( `Unexpected value ${prettyPrint(value)} for` + ` attribute "${name}". Expected: ${expected}`, ); } function serializeBinaryAttributeValue(value: unknown, name: string) { if (value instanceof Binary) return { id: value.id() }; throwInvalidAttributeValue(value, name, 'A Binary.'); } function serializeBooleanAttributeValue(value: unknown, name: string) { if (value === false || value === true) return value; throwInvalidAttributeValue(value, name, 'A Boolean.'); } function serializeDataLocatorAttributeValue(value: unknown, name: string) { if (value instanceof DataLocator) return value.toPojo(); throwInvalidAttributeValue(value, name, 'A DataLocator.'); } function serializeDateAttributeValue(value: unknown, name: string) { if (isDate(value)) return formatDateToString(value); if (isValidDateString(value)) return value; throwInvalidAttributeValue(value, name, 'A Date.'); } function serializeEnumAttributeValue( value: unknown, name: string, { values }: { values: readonly string[] }, ) { if (values.includes(value as string)) return value as string; const e = `Valid attribute values are contained in its "values" array [${values.join()}].`; throwInvalidAttributeValue(value, name, e); } function serializeFloatAttributeValue(value: unknown, name: string) { if (isValidFloat(value)) return value; let invalidValue = value; if (typeof value === 'number') { invalidValue = String(value); } throwInvalidAttributeValue( invalidValue, name, 'A Number, that is #isFinite().', ); } function serializeHtmlAttributeValue(value: unknown, name: string) { if (typeof value === 'string') return value; throwInvalidAttributeValue(value, name, 'A String.'); } function serializeIntegerAttributeValue(value: unknown, name: string) { if (isValidInteger(value)) return value; throwInvalidAttributeValue( value, name, 'A Number, that is #isSafeInteger().', ); } function serializeLinkAttributeValue(value: unknown, name: string) { if (isValidLinkInputValue(value)) return convertLinkToCmsApi(value); throwInvalidAttributeValue( value, name, 'A Link instance with a destination.', ); } function serializeLinklistAttributeValue(value: unknown, name: string) { if (Array.isArray(value) && value.every(isValidLinkInputValue)) { return value.map(convertLinkToCmsApi); } throwInvalidAttributeValue( value, name, 'An array of Link instances with destinations set.', ); } function serializeMultienumAttributeValue( value: unknown, name: string, { values }: { values: readonly string[] }, ) { if (!isStringArray(value)) { throwInvalidAttributeValue( value, name, `An array with values from ${prettyPrint(values)}.`, ); } const allowedValues = new Set(values); const forbiddenValues = value.filter((v) => !allowedValues.has(v)); if (forbiddenValues.length) { throwInvalidAttributeValue( value, name, `An array with values from ${prettyPrint( values, )}. Forbidden values: ${prettyPrint(forbiddenValues)}.`, ); } return value; } function serializeReferenceAttributeValue(value: unknown, name: string) { if (isValidReference(value)) return serializeSingleReferenceValue(value); throwInvalidAttributeValue(value, name, 'An Obj.'); } function serializeReferencelistAttributeValue(value: unknown, name: string) { if (isValidReferencelistValue(value)) { return value.map(serializeSingleReferenceValue); } throwInvalidAttributeValue(value, name, 'An array with Objs.'); } function serializeSingleReferenceValue( value: string | BasicObj | ObjUnavailable, ) { return typeof value === 'string' ? value : value.id(); } function isValidReference( value: unknown, ): value is string | BasicObj | ObjUnavailable { return ( value === '' || isObjId(value) || value instanceof BasicObj || value instanceof ObjUnavailable ); } function isValidReferencelistValue( value: unknown, ): value is (string | BasicObj | ObjUnavailable)[] { return Array.isArray(value) && value.every((v) => isValidReference(v)); } function serializeStringAttributeValue(value: unknown, name: string) { if (isValidString(value)) return value.toString(); throwInvalidAttributeValue(value, name, 'A String.'); } function serializeStringlistAttributeValue(value: unknown, name: string) { if (isStringOrNumberArray(value)) { const strings = value.map((v) => v.toString()); if (strings.some((v) => v.trim().length === 0)) { throwInvalidAttributeValue(value, name, 'An array of non-blank strings.'); } return strings; } throwInvalidAttributeValue(value, name, 'An array of strings.'); } function isValidString(value: unknown): value is string | number { return typeof value === 'string' || typeof value === 'number'; } function serializeWidgetAttributeValue(value: unknown, name: string): string { if (value instanceof BasicWidget) return value.id(); throwInvalidAttributeValue(value, name, 'An instance of Widget.'); } function serializeWidgetlistAttributeValue( value: unknown, name: string, ): string[] { if (value instanceof BasicWidget) { return serializeWidgetlistAttributeValue([value], name); } if (isBasicWidgetArray(value)) return value.map((v) => v.id()); throwInvalidAttributeValue(value, name, 'An array of Widget instances.'); } function isBasicWidgetArray(value: unknown): value is BasicWidget[] { return Array.isArray(value) && value.every((v) => v instanceof BasicWidget); } function isStringOrNumberArray( value: unknown, ): value is Array<string | number> { return Array.isArray(value) && value.every((v) => isValidString(v)); } function isValidLinkInputValue(value: unknown): value is ValidLinkInputValue { // check if value is backend compatible if (value instanceof BasicLink) return !value.isEmpty(); if (!isObject(value)) return false; if (isEmpty(Object.values(value).filter(Boolean))) return false; return Object.keys(value).every((key) => LINK_ATTRIBUTES.includes(key)); } const LINK_ATTRIBUTES = [ 'hash', 'obj_id', 'query', 'rel', 'target', 'title', 'url', ]; function convertCamelCasedAttributeName(name: string) { if (!isCamelCase(name)) { throw new ArgumentError('Attribute names have to be in camel case.'); } return underscore(name); } function convertLinkToCmsApi(value: ValidLinkInputValue): LinkJson { const cmsApiValue = value instanceof BasicLink ? convertBasicLinkToCmsApi(value) : convertLinkObjectToCmsApi(value); if (!cmsApiValue.rel) { delete cmsApiValue.rel; } return cmsApiValue; } function convertBasicLinkToCmsApi(basicLink: BasicLink): LinkJson { return { rel: basicLink.rel() || undefined, query: basicLink.query(), target: basicLink.target(), title: basicLink.title(), url: basicLink.url(), // lowercased property method obj_id: basicLink.objId(), // different property method fragment: basicLink.hash(), }; } function convertLinkObjectToCmsApi(value: Readonly<PlainLinkObject>): LinkJson { return { obj_id: value.obj_id || null, query: value.query || null, rel: value.rel || undefined, target: value.target || null, title: value.title || null, url: value.url || null, // different property key fragment: value.hash || null, }; }