import {Universe} from "./universe"; export type InterfaceType = 'named' | 'primitive' | 'list' | 'map' | 'generic' | 'reference'; export type PrimitiveType = 'id' | 'string' | 'integer' | 'float' | 'boolean' | 'void'; export type TemplateType = 'primitive' | 'list'; export type GenericValues = { [_: string]: Interface }; export type TemplateValues = { [_: string]: TemplateValue }; export interface TemplateValue { type: TemplateType; value?: string | string[]; source?: string; } export interface Interface { [_: string]: any; type?: InterfaceType; named?: NamedInterface; primitive?: PrimitiveInterface; list?: ListInterface; map?: MapInterface; generic?: GenericInterface; reference?: ReferenceInterface; } export interface TemplateInterfaceSlot { name: string; type: TemplateType; description: string; } export interface GenericInterfaceSlot { name: string; description: string; } export interface NamedInterface { [_: string]: any; name: string; template?: TemplateInterfaceSlot[]; generics?: GenericInterfaceSlot[]; schema: Interface; } export interface PrimitiveInterface { model?: string; value?: PrimitiveType; } export interface ListInterface { entry: Interface; } export interface TemplateExpansion { source: string; target: string; } export interface MapInterfaceEntry { key: string; expansion?: TemplateExpansion; schema: Interface; } export interface MapInterface { entries: MapInterfaceEntry[]; } export interface GenericInterface { name?: string; } export interface ReferenceInterface { generics?: GenericValues; template?: TemplateValues; name?: string; } export function interfaceComplexity(i: Interface): number { switch (i.type) { case 'list': return interfaceComplexity(i.list!.entry) + 1; case 'map': // eslint-disable-next-line no-case-declarations let complexity = 0; for (const e of i.map!.entries) { complexity += interfaceComplexity(e.schema); } return complexity + 1; case 'reference': return 2; } return 1; } export function subInterface(sub: Interface, sup: Interface, universe?: Universe, genericMap?: Map): boolean { if (!genericMap) { genericMap = new Map(); } if (sub.type !== sup.type) { return false; } switch (sub.type) { case 'map': if (!sub.map || !sup.map) { return false; } for (const e of sub.map.entries) { let found = false; for (const e2 of sup.map.entries) { if (e.key === e2.key) { if (!subInterface(e.schema, e2.schema)) { return false; } found = true; break; } } if (!found) { return false; } } return true; case 'list': if (!sub.list || !sup.list) { return false; } return subInterface(sub.list.entry, sup.list.entry); case 'primitive': if (!sub.primitive || !sup.primitive) { return false; } // TODO: Consider model inheritance! return sub.primitive.value === sup.primitive.value && sub.primitive.model === sup.primitive.model; case 'generic': if (!sub.generic?.name || !sup.generic?.name) { return false; } // When we do a structural comparison between two generics, we need to keep track of previous mappings // eslint-disable-next-line no-case-declarations const mp = genericMap.get(sub.generic.name); if (!mp) { genericMap.set(sub.generic.name, sup.generic.name); return true; } else { return mp === sup.generic.name; } case 'reference': if (!sub.reference?.name || !sup.reference?.name) { return false; } if (!universe) { return false; } // eslint-disable-next-line no-case-declarations const ref = universe.getInterface(sub.reference.name); // eslint-disable-next-line no-case-declarations const subRef = universe.getInterface(sup.reference.name); if (!ref || !subRef) { return false; } // TODO: Generics (and templates)! return subInterface(ref.schema, subRef.schema); } return false; } export function newValue(i: Interface, u?: Universe): any { switch (i.type) { case 'primitive': if (!i.primitive) { return undefined; } return newPrimitiveValue(i.primitive); case 'list': return []; case 'map': if (!i.map) { return undefined; } // eslint-disable-next-line no-case-declarations const val: {[_: string]: any} = {}; for (const e of i.map.entries) { val[e.key] = newValue(e.schema, u); } return val; case 'reference': if (!u || !i.reference?.name) { return undefined; } // eslint-disable-next-line no-case-declarations const ni = u.getInterface(i.reference.name); if (!ni) { return undefined } return newValue(ni.schema, u); } } function newPrimitiveValue(i: PrimitiveInterface): any { switch (i.value) { case 'string': return ''; case 'boolean': return false; case 'integer': return 0; case 'float': return 0; case 'id': return null; case 'void': return undefined; } return undefined; }