import { SelectOption } from '@prefecthq/prefect-design' import { Component } from 'vue' import { JsonInput } from '@/components' import { InvalidSchemaValueError } from '@/models/InvalidSchemaValueError' import { getSchemaPropertyAttrs, getSchemaPropertyComponentWithDefaultProps, getSchemaPropertyDefaultValidators, schemaPropertyComponentWithProps, SchemaPropertyComponentWithProps } from '@/services/schemas/utilities' import { schemaHas, SchemaProperty, SchemaPropertyInputAttrs, SchemaPropertyMeta, SchemaValue } from '@/types/schemas' import { Require } from '@/types/utilities' import { sameValue } from '@/utilities' import { isNumberArray, isStringArray } from '@/utilities/arrays' import { fieldRules, isJson, ValidationMethod, ValidationMethodFactory } from '@/utilities/validation' export type SchemaPropertyServiceSource = { property: SchemaProperty, level: number, } export type SchemaPropertyServiceConstructor = new (source: SchemaPropertyServiceSource) => SchemaPropertyService export abstract class SchemaPropertyService { /** * Converts a schema value from the ui into the correct value for the api */ protected abstract request(value: SchemaValue): SchemaValue /** * Converts a schema value from the api into the correct value for the ui */ protected abstract response(value: SchemaValue): SchemaValue /** * Returns the vue component and any props necessary to render the property in the schema form */ protected abstract get component(): SchemaPropertyComponentWithProps /** * Returns the value needed for the @property {PropertyComponentWithProps} property to be * rendered when no value exists or the value is invalid */ protected abstract get default(): SchemaValue /** * Can be extended to add property specific validation rules. Implemented here because this is not required */ protected get validators(): ValidationMethodFactory[] { return [] } /** * Can be extended to add property specific attrs rules. Implemented here because this is not required */ protected get attrs(): SchemaPropertyInputAttrs { return {} } protected property: SchemaProperty protected level: number protected withProps = schemaPropertyComponentWithProps public constructor({ property, level }: SchemaPropertyServiceSource) { this.property = property this.level = level } public mapResponseValue(value: SchemaValue): SchemaValue { try { return this.response(value) } catch (error) { if (!(error instanceof InvalidSchemaValueError)) { console.error(error) } } try { return this.response(this.default) } catch (error) { if (!(error instanceof InvalidSchemaValueError)) { console.error(error) } } return this.default } public mapRequestValue(value: SchemaValue): SchemaValue | undefined { if (this.isDefaultValue(value)) { return undefined } return this.request(value) } public getDefaultValue(): SchemaValue { return this.default } public getComponent(): SchemaPropertyComponentWithProps { if (this.component === null) { return this.component } return getSchemaPropertyComponentWithDefaultProps(this.component) } public getValidators(required: boolean): ValidationMethod[] { const { title = 'Property' } = this.property const defaults = getSchemaPropertyDefaultValidators(this.property, required) const validators = fieldRules(title, ...this.validators) if (this.componentIs(JsonInput)) { validators.push(isJson(title)) } return [...validators, ...defaults] } public getAttrs(): SchemaPropertyInputAttrs { const defaults = getSchemaPropertyAttrs(this.property) return { ...this.attrs, ...defaults } } public getMeta(required: boolean): SchemaPropertyMeta | null { const { component, props } = this.getComponent() ?? {} return { component, props, required, attrs: this.getAttrs(), validators: this.getValidators(required), } } protected componentIs(component: Component): boolean { return this.component?.component === component } protected invalid(): void { throw new InvalidSchemaValueError() } protected has(key: T): this is { property: SchemaProperty & Require } { return schemaHas(this.property, key) } protected getSelectOptions(): SelectOption[] { const options: SelectOption[] = [] const propertyEnum = this.property.enum const itemsEnum = this.property.items?.enum if (!propertyEnum && !itemsEnum) { return options } if (!this.property.meta?.required && !this.property.default) { options.push({ label: 'None', value: null }) } if (isStringArray(propertyEnum) || isNumberArray(propertyEnum)) { const mapped = propertyEnum.map(value => ({ label: `${value}`, value })) options.push(...mapped) } if (isStringArray(itemsEnum) || isNumberArray(itemsEnum)) { const mapped = itemsEnum.map(value => ({ label: `${value}`, value })) options.push(...mapped) } return options } protected isDefaultValue(value: SchemaValue): boolean { return sameValue(value, this.default) } }