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.

408 lines (340 loc) 11.8 kB
import difference from 'lodash-es/difference'; 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 { 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]; serializedAttributes[serializedName] = serializeAttributeEntry( value, name, typeInfo! ); } }); return serializedAttributes; } function serializeAttributeEntry<Type extends CmsAttributeType>( value: unknown, name: string, typeInfo: BasicTypeInfo<Type> ): ObjJsonPatch[keyof ObjJsonPatch] { 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 forbiddenValues = difference(value, values); 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 ( typeof value === 'string' || 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)) { return value.map((v) => v.toString()); } 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; const invalidKeys = difference( Object.keys(value as { [key: string]: unknown }), ['hash', 'obj_id', 'query', 'rel', 'target', 'title', 'url'] ); return isEmpty(invalidKeys); } 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, }; }