diff --git a/src/Framework/Framework/Resources/Scripts/global-declarations.ts b/src/Framework/Framework/Resources/Scripts/global-declarations.ts index 848700e9fe..e875e05ef8 100644 --- a/src/Framework/Framework/Resources/Scripts/global-declarations.ts +++ b/src/Framework/Framework/Resources/Scripts/global-declarations.ts @@ -248,16 +248,19 @@ type TypeMap = { } type DynamicTypeMetadata = { + debugName?: string, type: "dynamic" } type ObjectTypeMetadata = { type: "object", + debugName?: string, properties: { [prop: string]: PropertyMetadata } } type EnumTypeMetadata = { type: "enum", + debugName?: string, values: { [name: string]: number }, isFlags?: boolean } diff --git a/src/Framework/Framework/Resources/Scripts/metadata/coercer.ts b/src/Framework/Framework/Resources/Scripts/metadata/coercer.ts index a7e58b4bf9..6d83ed6e8b 100644 --- a/src/Framework/Framework/Resources/Scripts/metadata/coercer.ts +++ b/src/Framework/Framework/Resources/Scripts/metadata/coercer.ts @@ -3,7 +3,7 @@ import { CoerceError } from "../shared-classes"; import { keys } from "../utils/objects"; import { tryCoerceEnum } from "./enums"; import { primitiveTypes } from "./primitiveTypes"; -import { getObjectTypeInfo, getTypeInfo } from "./typeMap"; +import { formatTypeName, getObjectTypeInfo, getTypeInfo } from "./typeMap"; /** * Validates type of value @@ -45,7 +45,7 @@ export function tryCoerce(value: any, type: TypeDefinition | null | undefined, o } } } - return new CoerceError(`Unsupported type metadata ${JSON.stringify(type)}!`); + return new CoerceError(`Unsupported type metadata ${formatTypeName(type)}!`); } const result = core(); @@ -100,7 +100,7 @@ function tryCoerceArray(value: any, innerType: TypeDefinition, originalValue: an return { value: items, wasCoerced: true }; } } - return new CoerceError(`Value '${JSON.stringify(value)}' is not an array of type '${JSON.stringify(innerType)}'.`); + return new CoerceError(`Value '${JSON.stringify(value)}' is not an array of type '${formatTypeName(innerType)}'.`); } function tryCoercePrimitiveType(value: any, type: string): CoerceResult { diff --git a/src/Framework/Framework/Resources/Scripts/metadata/typeMap.ts b/src/Framework/Framework/Resources/Scripts/metadata/typeMap.ts index cb6052aa8c..63ed224006 100644 --- a/src/Framework/Framework/Resources/Scripts/metadata/typeMap.ts +++ b/src/Framework/Framework/Resources/Scripts/metadata/typeMap.ts @@ -63,3 +63,28 @@ export function areObjectTypesEqual(currentValue: any, newVal: any): boolean { } return false; } + + +export function formatTypeName(type: TypeDefinition, prefix = "", suffix = ""): string { + if (!compileConstants.debug) + return JSON.stringify(type) + + if (typeof type === "string") { + let debugName = types[type]?.debugName + if (debugName) + return `${prefix}${debugName}${suffix} (${prefix}${type}${suffix})` + else + return prefix + type + suffix + } + if (Array.isArray(type)) { + return formatTypeName(type[0], prefix, "[]" + suffix) + } + if (type.type == "nullable") { + return formatTypeName(type.inner, prefix, "?" + suffix) + } + if (type.type == "dynamic") { + return prefix + "dynamic" + suffix + } + const typeCheck: never = type + return undefined as any +} diff --git a/src/Framework/Framework/Resources/Scripts/state-manager.ts b/src/Framework/Framework/Resources/Scripts/state-manager.ts index 3dc584242b..737ecdd02f 100644 --- a/src/Framework/Framework/Resources/Scripts/state-manager.ts +++ b/src/Framework/Framework/Resources/Scripts/state-manager.ts @@ -3,7 +3,7 @@ import { createArray, defineConstantProperty, isPrimitive, keys } from "./utils/objects"; import { DotvvmEvent } from "./events"; import { extendToObservableArrayIfRequired } from "./serialization/deserialize" -import { areObjectTypesEqual, getObjectTypeInfo } from "./metadata/typeMap"; +import { areObjectTypesEqual, formatTypeName, getObjectTypeInfo } from "./metadata/typeMap"; import { coerce } from "./metadata/coercer"; import { patchViewModel } from "./postback/updater"; import { hackInvokeNotifySubscribers } from "./utils/knockout"; @@ -166,7 +166,7 @@ class FakeObservableObject implements UpdatableObjectExtension } } } else if (!isDynamic && p.indexOf("$") !== 0) { - logWarning("state-manager", `Unknown property '${p}' set on an object of type ${typeId}.`); + logWarning("state-manager", `Unknown property '${p}' set on an object of type ${formatTypeName(typeId)}.`); } this[internalPropCache][p] = newObs diff --git a/src/Framework/Framework/Resources/Scripts/tests/coercer.test.ts b/src/Framework/Framework/Resources/Scripts/tests/coercer.test.ts index 2d99a2a3ee..81e252ddef 100644 --- a/src/Framework/Framework/Resources/Scripts/tests/coercer.test.ts +++ b/src/Framework/Framework/Resources/Scripts/tests/coercer.test.ts @@ -1,4 +1,5 @@ import { tryCoerce } from "../metadata/coercer" +import { formatTypeName } from "../metadata/typeMap"; import { initDotvvm } from "./helper"; initDotvvm({ @@ -9,6 +10,7 @@ initDotvvm({ typeMetadata: { t1: { type: "object", + debugName: "MyType1", properties: { a: { type: "String" @@ -607,3 +609,13 @@ test("dynamic - valid, object of unknown type, nested known object", () => { expect(result.wasCoerced).toBeFalsy(); expect(result.value).toEqual({ inner: { $type: "t1", a: "aa" } }); }) + +test("formatTypeName", () => { + expect(formatTypeName("t1")).toBe("MyType1 (t1)"); + expect(formatTypeName("t2")).toBe("t2"); + expect(formatTypeName([ "t1" ])).toBe("MyType1[] (t1[])"); + expect(formatTypeName([ "t2" ])).toBe("t2[]"); + expect(formatTypeName({ type: "nullable", inner: "t1" })).toBe("MyType1? (t1?)"); + expect(formatTypeName({ type: "nullable", inner: "t2" })).toBe("t2?"); + expect(formatTypeName([ { type:"nullable", inner: [ [ "t1" ] ] }])).toBe("MyType1[][]?[] (t1[][]?[])"); +})