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) )
}