File

lib/core/src/items/questions/questions.commons.ts

Index

Properties

Properties

id
id: string
Type : string
max
max: number
Type : number
Optional
meaning
meaning: string
Type : string
Optional
min
min: number
Type : number
Optional
options
options: QuestionOptionConfig[]
Type : QuestionOptionConfig[]
Optional
tags
tags: string[]
Type : string[]
Optional
translations
translations: TranslationList
Type : TranslationList
type
type: AnswerType
Type : AnswerType
unit
unit: string
Type : string
Optional
import	{
			isErrorFree,
			has,
			assert,
			assertProperty
		}									from '../../utils'

export interface TranslationList {
	[index:string]:	string
}

export interface StringOptionConfig{
	value:			string,
	meaning?:		string,
	boolean?:		boolean //legacy remove asap
	translations:	TranslationList
}

export interface NumberOptionConfig{
	value:			number,
	meaning?:		string,
	boolean?:		boolean //legacy remove asap
	translations?:	TranslationList
}

export interface BooleanOptionConfig{
	value:			boolean,
	meaning?:		string,
	boolean?:		boolean //legacy remove asap
	translations?:	TranslationList
}


export type QuestionOptionConfig = StringOptionConfig | NumberOptionConfig | BooleanOptionConfig


export type AnswerType = 'decimal' | 'integer' | 'string' | 'boolean' | 'unknown'

export interface QuestionConfig {
	id:				string,
	type:			AnswerType,				// string, integer, decimal...
	translations:	TranslationList,
	meaning?:		string,					// TODO: instead add extra key for TranslationList
	min?:			number,
	max?:			number,
	options?:		QuestionOptionConfig[],
	tags?:			string[],
	unit?:			string,
	//note?:			string
}


export function assertNumberOptionConfig(x:unknown): asserts x is NumberOptionConfig {

	assertProperty(x, 'value', 			"assertNumberOptionConfig: missing .value")
	assert(Number.isFinite(x.value),	"assertNumberOptionConfig: .value must be a finite number")

	if( has(x,'translations') && x.translations){
		assert(isTranslationList(x.translations), "assertNumberOptionConfig: if exists, .translations must be a TranslationList")
	}

}

export function isNumberOptionConfig(x:unknown): x is NumberOptionConfig {
	return isErrorFree( () => assertNumberOptionConfig(x) )
}


export function isStringOptionConfig(x:unknown): x is StringOptionConfig {

	if(!has(x, 'value'))					return false
	if( typeof x.value != 'string') 		return false
	if(!has(x, 'translations'))				return false
	if(!isTranslationList(x.translations))	return false

	return true
}

export function isBooleanOptionConfig(x:unknown): x is BooleanOptionConfig {

	if(!has(x, 'value'))					return false
	if( typeof x.value != 'boolean') 		return false
	if(!has(x, 'translations'))				return false
	if(!isTranslationList(x.translations))	return false

	return true
}


export function isAnswerType(x:unknown): x is AnswerType {
	if(typeof x != 'string') return false
	return ['decimal', 'integer', 'string', 'boolean', 'unknown'].includes(x)
}



export function isTranslationList(x:unknown): x is TranslationList {

	if(typeof x != 'object') 	return false

	if(Object.keys(x).length == 0) return false
	if(Object.keys(x)	.some(	(key:unknown) 	=> typeof key 	!= 'string' ) ) return false
	if(Object.values(x)	.some(	(value:unknown)	=> typeof value	!= 'string'	) ) return false
	if(Object.values(x)	.every(	(value:unknown)	=> value == '') ) 				return false

	return true
}



export function assertQuestionConfig(x:unknown): asserts x is QuestionConfig {

	assertProperty(x, ['id', 'type', 'translations'])

	const type 			= x.type
	const id			= x.id
	const translations 	= x.translations

	assert(typeof id 	== 'string', 		"assertQuestionConfig: .id must be a string.", id)
	assert(isAnswerType(type),				`assertQuestionConfig: unknown answer type: ${String(type)}`, type)
	assert(isTranslationList(translations),	"assertQuestionConfig: invalid .translations: ", translations)


	if(has(x,'min')) 	assert(['number', 'undefined'].includes(typeof x.min), `assertQuestionConfig: .min must be a number or undefined, got:${typeof x.min}`, x.min)
	if(has(x,'max'))	assert(['number', 'undefined'].includes(typeof x.max), `assertQuestionConfig: .max must be a number or undefined, got:${typeof x.max}`, x.max)

	if(has(x,'min','max')){
		assert(x.min === undefined || x.max === undefined  || x.min <= x.max, `assertQuestionConfig: .max must be greater or equal to .min`, x)
	}


	if(!has(x,'options') || x.options == null || x.options == undefined) return;

	const options 		= x.options

	assert(Array.isArray(options),  		"assertQuestionConfig: if present .options must be an array." )

	const oLength		= options.length
	const eLength		= Object.values(options).length // this will differ from options.length, if empty slots are present.

	assert( oLength == eLength,				"assertQuestionConfig: .option must not include empty slots" )

	assert( type != 'unknown',		 		"assertQuestionConfig: config cannot have .type == 'unknown' and have .options at the same time." )


	const stringType 	= type == 'string'

	if(stringType) assert( options.every( isStringOptionConfig), "assertQuestionConfig: configs with .type 'string' must have string labels and only have string valued .options.")

	const numberType	= ['decimal', 'integer'].includes(type)

	if(numberType) options.forEach(assertNumberOptionConfig)

	const booleanType	= type == 'boolean'

	if(booleanType) assert( options.every( isBooleanOptionConfig), "assertQuestionConfig: configs with .type 'boolean' must have string labels and only have boolean valued .options.")

}

export function isQuestionConfig(x:unknown): x is QuestionConfig {
	return isErrorFree( () => assertQuestionConfig(x) )
}

results matching ""

    No results matching ""