import isEmpty from 'lodash/isEmpty' ; import get from 'lodash/get' ; import endsWith from 'lodash/endsWith' ; import last from 'lodash/last' ; import isArray from 'lodash/isArray' ; import reduce from 'lodash/reduce' ; import toPairs from 'lodash/toPairs' ; import includes from 'lodash/includes' ; import { Categorization , ControlElement , JsonSchema , UISchemaElement } from '../models' ; import { deriveTypes , hasType , resolveSchema } from '../util' ; type { number } export const NOT_APPLICABLE = - 1 ; /**
 * A tester is a function that receives an UI schema and a JSON schema and returns a boolean.
 */ export type Tester = ( uischema : UISchemaElement , schema : JsonSchema ) => boolean ; /**
 * A ranked tester associates a tester with a number.
 */ export type RankedTester = ( uischema : UISchemaElement , schema : JsonSchema ) => number ; export const isControl = ( uischema ) : uischema is ControlElement => ! isEmpty ( uischema ) && uischema . scope !== undefined ; param { ( JsonSchema ) => boolean } predicate export const schemaMatches = ( predicate ) : Tester => ( uischema , schema ) : boolean => { if ( isEmpty ( uischema ) || ! isControl ( uischema ) ) { return false ; } if ( isEmpty ( schema ) ) { return false ; } const schemaPath = uischema . scope ; if ( isEmpty ( schemaPath ) ) { return false ; } let currentDataSchema = schema ; if ( hasType ( schema , 'object' ) ) { currentDataSchema = resolveSchema ( schema , schemaPath ) ; } if ( currentDataSchema === undefined ) { return false ; } return predicate ( currentDataSchema ) ; } ; export const schemaSubPathMatches = ( subPath , predicate ) : Tester => ( uischema , schema ) : boolean => { if ( isEmpty ( uischema ) || ! isControl ( uischema ) ) { return false ; } const schemaPath = uischema . scope ; let currentDataSchema = schema ; if ( hasType ( schema , 'object' ) ) { currentDataSchema = resolveSchema ( schema , schemaPath ) ; } currentDataSchema = get ( currentDataSchema , subPath ) ; if ( currentDataSchema === undefined ) { return false ; } return predicate ( currentDataSchema ) ; } ; param { string } expectedType export const schemaTypeIs = ( expectedType ) : Tester => schemaMatches ( schema => ! isEmpty ( schema ) && hasType ( schema , expectedType ) ) ; param { string } expectedFormat export const formatIs = ( expectedFormat ) : Tester => schemaMatches ( schema => ! isEmpty ( schema ) && schema . format === expectedFormat && schema . type === 'string' ) ; param { string } expected export const uiTypeIs = ( expected ) : Tester => ( uischema ) : boolean => ! isEmpty ( uischema ) && uischema . type === expected ; param { string } optionName param { any } optionValue export const optionIs = ( optionName , optionValue ) : Tester => ( uischema ) : boolean => { if ( isEmpty ( uischema ) ) { return false ; } const options = uischema . options ; return ! isEmpty ( options ) && options [ optionName ] === optionValue ; } ; param { string } expected export const scopeEndsWith = ( expected ) : Tester => ( uischema ) : boolean => { if ( isEmpty ( expected ) || ! isControl ( uischema ) ) { return false ; } return endsWith ( uischema . scope , expected ) ; } ; param { string } expected export const scopeEndIs = ( expected ) : Tester => ( uischema ) : boolean => { if ( isEmpty ( expected ) || ! isControl ( uischema ) ) { return false ; } const schemaPath = uischema . scope ; return ! isEmpty ( schemaPath ) && last ( schemaPath . split ( '/' ) ) === expected ; } ; param { Array < Tester > } testers export const and = ( ... testers ) : Tester => ( uischema , schema ) => testers . reduce ( ( acc , tester ) => acc && tester ( uischema , schema ) , true ) ; param { Array < Tester > } testers export const or = ( ... testers ) : Tester => ( uischema , schema ) => testers . reduce ( ( acc , tester ) => acc || tester ( uischema , schema ) , false ) ; param { number } rank param { Tester } tester export const rankWith = ( rank , tester ) => ( uischema , schema ) : number => { if ( tester ( uischema , schema ) ) { return rank ; } return NOT_APPLICABLE ; } ; export const withIncreasedRank = ( by , rankedTester ) => ( uischema , schema ) : number => { const rank = rankedTester ( uischema , schema ) ; if ( rank === NOT_APPLICABLE ) { return NOT_APPLICABLE ; } return rank + by ; } ; type { RankedTester } export const isBooleanControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'boolean' ) ) ; export const isObjectControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'object' ) ) ; export const isAllOfControl = and ( uiTypeIs ( 'Control' ) , schemaMatches ( schema => schema . hasOwnProperty ( 'allOf' ) ) ) ; export const isAnyOfControl = and ( uiTypeIs ( 'Control' ) , schemaMatches ( schema => schema . hasOwnProperty ( 'anyOf' ) ) ) ; export const isOneOfControl = and ( uiTypeIs ( 'Control' ) , schemaMatches ( schema => schema . hasOwnProperty ( 'oneOf' ) ) ) ; type { Tester } export const isEnumControl = and ( uiTypeIs ( 'Control' ) , or ( schemaMatches ( schema => schema . hasOwnProperty ( 'enum' ) ) , schemaMatches ( schema => schema . hasOwnProperty ( 'const' ) ) ) ) ; type { Tester } export const isOneOfEnumControl = and ( uiTypeIs ( 'Control' ) , schemaMatches ( schema => schema . hasOwnProperty ( 'oneOf' ) && ( schema . oneOf as JsonSchema [ ] ) . every ( s => s . const !== undefined ) ) ) ; type { Tester } export const isIntegerControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'integer' ) ) ; type { Tester } export const isNumberControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'number' ) ) ; type { Tester } export const isStringControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'string' ) ) ; type { Tester } export const isMultiLineControl = and ( uiTypeIs ( 'Control' ) , optionIs ( 'multi' , true ) ) ; type { Tester } export const isDateControl = and ( uiTypeIs ( 'Control' ) , or ( formatIs ( 'date' ) , optionIs ( 'format' , 'date' ) ) ) ; type { Tester } export const isTimeControl = and ( uiTypeIs ( 'Control' ) , or ( formatIs ( 'time' ) , optionIs ( 'format' , 'time' ) ) ) ; type { Tester } export const isDateTimeControl = and ( uiTypeIs ( 'Control' ) , or ( formatIs ( 'date-time' ) , optionIs ( 'format' , 'date-time' ) ) ) ; type { Tester } export const isObjectArray = and ( schemaMatches ( schema => hasType ( schema , 'array' ) && ! Array . isArray ( schema . items ) ) , schemaSubPathMatches ( 'items' , schema => hasType ( schema , 'object' ) ) ) ; type { Tester } export const isObjectArrayControl = and ( uiTypeIs ( 'Control' ) , isObjectArray ) ; const traverse = ( any , pred ) : boolean => { if ( isArray ( any ) ) { return reduce ( any , ( acc , el ) => acc || traverse ( el , pred ) , false ) ; } if ( pred ( any ) ) { return true ; } if ( any . items ) { return traverse ( any . items , pred ) ; } if ( any . properties ) { return reduce ( toPairs ( any . properties ) , ( acc , [ _key , val ] ) => acc || traverse ( val , pred ) , false ) ; } return false ; } ; export const isObjectArrayWithNesting = ( uischema , schema ) : boolean => { if ( ! uiTypeIs ( 'Control' ) ( uischema , schema ) ) { return false ; } const schemaPath = ( uischema as ControlElement ) . scope ; const resolvedSchema = resolveSchema ( schema , schemaPath ) ; const wantedNestingByType = { object : 2 , array : 1 } ; if ( resolvedSchema !== undefined && resolvedSchema . items !== undefined ) { if ( traverse ( resolvedSchema . items , val => { if ( val === schema ) { return false ; } if ( typeof val . type !== 'string' ) { return true ; } const typeCount = wantedNestingByType [ val . type ] ; if ( typeCount === undefined ) { return false ; } wantedNestingByType [ val . type ] = typeCount - 1 ; return wantedNestingByType [ val . type ] === 0 ; } ) ) { return true ; } if ( uischema . options && uischema . options . detail ) { if ( typeof uischema . options . detail === 'string' ) { return uischema . options . detail . toUpperCase ( ) !== 'DEFAULT' ; } else if ( typeof uischema . options . detail === 'object' && uischema . options . detail . type ) { return true ; } } } return false ; } ; /**
 * Synonym for isObjectArrayControl
 */ export const isArrayObjectControl = isObjectArrayControl ; type { Tester } export const isPrimitiveArrayControl = and ( uiTypeIs ( 'Control' ) , schemaMatches ( schema => deriveTypes ( schema ) . length !== 0 && ! Array . isArray ( schema . items ) ) , schemaSubPathMatches ( 'items' , schema => { const types = deriveTypes ( schema ) ; return ( types . length === 1 && includes ( [ 'integer' , 'number' , 'boolean' , 'string' ] , types [ 0 ] ) ) ; } ) ) ; type { Tester } export const isRangeControl = and ( uiTypeIs ( 'Control' ) , or ( schemaTypeIs ( 'number' ) , schemaTypeIs ( 'integer' ) ) , schemaMatches ( schema => schema . hasOwnProperty ( 'maximum' ) && schema . hasOwnProperty ( 'minimum' ) && schema . hasOwnProperty ( 'default' ) ) , optionIs ( 'slider' , true ) ) ; type { Tester } export const isNumberFormatControl = and ( uiTypeIs ( 'Control' ) , schemaTypeIs ( 'integer' ) , optionIs ( 'format' , true ) ) ; export const isCategorization = ( category ) : category is Categorization => category . type === 'Categorization' ; export const isCategory = ( uischema ) : boolean => uischema . type === 'Category' ; export const hasCategory = ( categorization ) : boolean => { if ( isEmpty ( categorization . elements ) ) { return false ; } return categorization . elements . map ( elem => isCategorization ( elem ) ? hasCategory ( elem ) : isCategory ( elem ) ) . reduce ( ( prev , curr ) => prev && curr , true ) ; } ; export const categorizationHasCategory = ( uischema ) => hasCategory ( uischema as Categorization ) ; export const not = ( tester ) : Tester => ( uischema , schema ) => ! tester ( uischema , schema ) ;