import { Const, type Static, StringEnum, StringRecord, Type } from "core/utils"; import type { EntityManager } from "data"; import { TransformPersistFailedException } from "../errors"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; export const enumFieldConfigSchema = Type.Composite( [ Type.Object({ default_value: Type.Optional(Type.String()), options: Type.Optional( Type.Union([ Type.Object( { type: Const("strings"), values: Type.Array(Type.String()), }, { title: "Strings" }, ), Type.Object( { type: Const("objects"), values: Type.Array( Type.Object({ label: Type.String(), value: Type.String(), }), ), }, { title: "Objects", additionalProperties: false, }, ), ]), ), }), baseFieldConfigSchema, ], { additionalProperties: false, }, ); export type EnumFieldConfig = Static; export class EnumField extends Field< EnumFieldConfig, TypeOverride, Required > { override readonly type = "enum"; constructor(name: string, config: Partial) { super(name, config); /*if (this.config.options.values.length === 0) { throw new Error(`Enum field "${this.name}" requires at least one option`); }*/ if (this.config.default_value && !this.isValidValue(this.config.default_value)) { throw new Error(`Default value "${this.config.default_value}" is not a valid option`); } } protected getSchema() { return enumFieldConfigSchema; } getOptions(): { label: string; value: string }[] { const options = this.config?.options ?? { type: "strings", values: [] }; /*if (options.values?.length === 0) { throw new Error(`Enum field "${this.name}" requires at least one option`); }*/ if (options.type === "strings") { return options.values?.map((option) => ({ label: option, value: option })); } return options?.values; } isValidValue(value: string): boolean { const valid_values = this.getOptions().map((option) => option.value); return valid_values.includes(value); } override getValue(value: any, context: TRenderContext) { if (!this.isValidValue(value)) { return this.hasDefault() ? this.getDefault() : null; } switch (context) { case "table": return this.getOptions().find((option) => option.value === value)?.label ?? value; } return value; } /** * Transform value after retrieving from database * @param value */ override transformRetrieve(value: string | null): string | null { const val = super.transformRetrieve(value); if (val === null && this.hasDefault()) { return this.getDefault(); } if (!this.isValidValue(val)) { return this.hasDefault() ? this.getDefault() : null; } return val; } override async transformPersist( _value: any, em: EntityManager, context: TActionContext, ): Promise { const value = await super.transformPersist(_value, em, context); if (this.nullish(value)) return value; if (!this.isValidValue(value)) { throw new TransformPersistFailedException( `Field "${this.name}" must be one of the following values: ${this.getOptions() .map((o) => o.value) .join(", ")}`, ); } return value; } override toJsonSchema() { const options = this.config?.options ?? { type: "strings", values: [] }; const values = options.values?.map((option) => (typeof option === "string" ? option : option.value)) ?? []; return this.toSchemaWrapIfRequired( StringEnum(values, { default: this.getDefault(), }), ); } }