import { isRecord } from '@ankhorage/utility/object'; import { isOptionalString } from '@ankhorage/utility/string'; import { isManifestValue } from './isManifestValue'; /*** Validate every component binding in the authored registry. */ export function isComponentDataBindingRegistry(value: unknown): boolean { return ( isRecord(value) && Object.entries(value).every( ([componentId, binding]) => isComponentDataBinding(binding) && isRecord(binding) && binding.componentId === componentId, ) ); } /*** Validate literal, operation and path-based binding sources. */ export function isBindingValueSource(value: unknown): boolean { return ( isRecord(value) && typeof value.kind === 'string' && ['context', 'event', 'literal', 'operation', 'state'].includes(value.kind) && (value.kind === 'literal' ? isManifestValue(value.value) : value.kind === 'operation' ? isBindingOperationRef(value.operation) && isOptionalString(value.path) : typeof value.path === 'string') ); } /*** Validate a screen operation loader and its optional input mapping. */ export function isScreenDataLoaderDefinition(value: unknown): boolean { return ( isRecord(value) && value.kind === 'operation' && isOptionalString(value.id) && isBindingOperationRef(value.operation) && (value.input === undefined || isBindingInputMap(value.input)) ); } /*** Validate component identity and its property and event bindings. */ function isComponentDataBinding(value: unknown): boolean { return ( isRecord(value) && typeof value.componentId === 'string' && isOptionalString(value.componentType) && (value.props === undefined || (isRecord(value.props) && Object.values(value.props).every(isPropBinding))) && (value.events === undefined || (isRecord(value.events) && Object.values(value.events).every( (bindings) => Array.isArray(bindings) && bindings.every(isEventBinding), ))) ); } /*** Validate a property binding with optional transforms and fallback behavior. */ function isPropBinding(value: unknown): boolean { return ( isRecord(value) && isBindingValueSource(value.source) && (value.fallback === undefined || isBindingFallback(value.fallback)) && (value.loading === undefined || isBindingLifecycleBehavior(value.loading)) && (value.error === undefined || isBindingLifecycleBehavior(value.error)) && (value.empty === undefined || isBindingLifecycleBehavior(value.empty)) && isOptionalBindingTransforms(value.transforms) ); } /*** Validate an event binding and its declared target. */ function isEventBinding(value: unknown): boolean { return ( isRecord(value) && isEventBindingTarget(value.target) && (value.input === undefined || isBindingInputMap(value.input)) && (value.when === undefined || isBindingCondition(value.when)) ); } /*** Validate the selected operation or action event target. */ function isEventBindingTarget(value: unknown): boolean { return ( isRecord(value) && ((value.kind === 'action' && typeof value.type === 'string') || (value.kind === 'operation' && isBindingOperationRef(value.operation))) ); } /*** Validate a binding condition and its source value. */ function isBindingCondition(value: unknown): boolean { return ( isRecord(value) && isBindingValueSource(value.source) && ['eq', 'exists', 'neq', 'notExists'].includes(String(value.operator)) && (value.value === undefined || isManifestValue(value.value)) ); } /*** Validate API and operation identities with an optional endpoint reference. */ function isBindingOperationRef(value: unknown): boolean { return ( isRecord(value) && typeof value.apiId === 'string' && typeof value.operationId === 'string' && isOptionalString(value.endpointId) ); } /*** Validate authored binding fallback values. */ function isBindingFallback(value: unknown): boolean { return ( isRecord(value) && (value.value === undefined || isManifestValue(value.value)) && (value.source === undefined || isBindingValueSource(value.source)) ); } /*** Validate loading, empty and error binding behavior. */ function isBindingLifecycleBehavior(value: unknown): boolean { return ( isRecord(value) && typeof value.state === 'string' && ['empty', 'error', 'loading'].includes(value.state) && (value.fallback === undefined || isBindingFallback(value.fallback)) && isOptionalString(value.message) ); } /*** Validate optional ordered binding transformations. */ function isOptionalBindingTransforms(value: unknown): boolean { return ( value === undefined || (Array.isArray(value) && value.every((transform) => ['lowercase', 'trim', 'uppercase'].includes(String(transform)))) ); } /*** Validate every value in an operation input mapping. */ function isBindingInputMap(value: unknown): boolean { return isRecord(value) && Object.values(value).every(isBindingInputValue); } /*** Validate recursive array, object, literal and source operation inputs. */ function isBindingInputValue(value: unknown): boolean { return ( isRecord(value) && typeof value.kind === 'string' && ((value.kind === 'array' && Array.isArray(value.items) && value.items.every(isBindingInputValue)) || (value.kind === 'literal' && isManifestValue(value.value)) || (value.kind === 'object' && isBindingInputMap(value.fields)) || (value.kind === 'source' && isBindingValueSource(value.source) && isOptionalBindingTransforms(value.transforms))) ); }