/** * Type alias for enum values - can be an array of strings or undefined */ export type EnumType = string[] | undefined /** * Represents a property in a JSON schema with various validation and display options */ export interface SchemaProperty { type: string | undefined description?: string pattern?: string default?: any enum?: EnumType contentEncoding?: 'base64' | 'binary' format?: string items?: { type?: 'string' | 'number' | 'bytes' | 'object' | 'resource' contentEncoding?: 'base64' enum?: string[] resourceType?: string properties?: { [name: string]: SchemaProperty } } min?: number max?: number currency?: string currencyLocale?: string multiselect?: boolean customErrorMessage?: string properties?: { [name: string]: SchemaProperty } required?: string[] showExpr?: string password?: boolean order?: string[] nullable?: boolean dateFormat?: string title?: string placeholder?: string oneOf?: SchemaProperty[] originalType?: string } /** * Converts argument signature types to JSON schema properties. * This function handles various Windmill-specific types and converts them * to standard JSON schema format while preserving existing property metadata. * * @param t - The argument signature type definition (can be string or complex object types) * @param oldS - Existing schema property to update with new type information */ export function argSigToJsonSchemaType( t: | string | { resource: string | null } | { list: | (string | { name?: string; props?: { key: string; typ: any }[] }) | { str: any } | { object: { name?: string; props?: { key: string; typ: any }[] } } | null } | { dynselect: string } | { dynmultiselect: string } | { str: string[] | null } | { object: { name?: string; props?: { key: string; typ: any }[] } } | { oneof: { label: string properties: { key: string; typ: any }[] }[] }, oldS: SchemaProperty ): void { const newS: SchemaProperty = { type: '' } if (t === 'int') { newS.type = 'integer' } else if (t === 'float') { newS.type = 'number' } else if (t === 'bool') { newS.type = 'boolean' } else if (t === 'email') { newS.type = 'string' newS.format = 'email' } else if (t === 'sql') { newS.type = 'string' newS.format = 'sql' } else if (t === 'yaml') { newS.type = 'string' newS.format = 'yaml' } else if (t === 'bytes') { newS.type = 'string' newS.contentEncoding = 'base64' newS.originalType = 'bytes' } else if (t === 'datetime') { newS.type = 'string' newS.format = 'date-time' } else if (typeof t !== 'string' && 'oneof' in t) { newS.type = 'object' if (t.oneof) { newS.oneOf = t.oneof.map((obj) => { const oldObjS = oldS.oneOf?.find((o) => o?.title === obj.label) ?? undefined const properties: Record = {} for (const prop of obj.properties) { if (oldObjS?.properties && prop.key in oldObjS?.properties) { properties[prop.key] = oldObjS?.properties[prop.key] } else { properties[prop.key] = { description: '', type: '' } } argSigToJsonSchemaType(prop.typ, properties[prop.key]) } return { type: 'object', title: obj.label, properties, order: oldObjS?.order ?? undefined } }) } } else if (typeof t !== 'string' && `object` in t) { newS.type = 'object' if (t.object.name) { newS.format = `resource-${t.object.name}` } if (t.object.props) { const properties: Record = {} for (const prop of t.object.props) { if (oldS.properties && prop.key in oldS.properties) { properties[prop.key] = oldS.properties[prop.key] } else { properties[prop.key] = { description: '', type: '' } } argSigToJsonSchemaType(prop.typ, properties[prop.key]) } newS.properties = properties } } else if (typeof t !== 'string' && `str` in t) { newS.type = 'string' if (t.str) { newS.originalType = 'enum' newS.enum = t.str } else if (oldS.originalType == 'string' && oldS.enum) { newS.originalType = 'string' newS.enum = oldS.enum } else { newS.originalType = 'string' newS.enum = undefined } } else if (typeof t !== 'string' && `resource` in t) { newS.type = 'object' newS.format = `resource-${t.resource}` } else if (typeof t !== 'string' && `dynselect` in t) { newS.type = 'object' newS.format = `dynselect-${t.dynselect}` } else if (typeof t !== 'string' && `dynmultiselect` in t) { newS.type = 'object' newS.format = `dynmultiselect-${t.dynmultiselect}` } else if (typeof t !== 'string' && `list` in t) { newS.type = 'array' if (t.list === 'int' || t.list === 'float') { newS.items = { type: 'number' } newS.originalType = 'number[]' } else if (t.list === 'bytes') { newS.items = { type: 'string', contentEncoding: 'base64' } newS.originalType = 'bytes[]' } else if (t.list && typeof t.list == 'object' && 'str' in t.list && t.list.str) { newS.items = { type: 'string', enum: t.list.str } newS.originalType = 'enum[]' } else if (t.list == 'string' || (t.list && typeof t.list == 'object' && 'str' in t.list)) { newS.items = { type: 'string', enum: oldS.items?.enum } newS.originalType = 'string[]' } else if (t.list && typeof t.list == 'object' && 'resource' in t.list && t.list.resource) { newS.items = { type: 'resource', resourceType: t.list.resource as string } newS.originalType = 'resource[]' } else if (t.list && typeof t.list == 'object' && 'object' in t.list && t.list.object) { if (t.list.object.name) { newS.format = `resource-${t.list.object.name}` } if (t.list.object.props && t.list.object.props.length > 0) { const properties: Record = {} for (const prop of t.list.object.props) { properties[prop.key] = { description: '', type: '' } argSigToJsonSchemaType(prop.typ, properties[prop.key]) } newS.items = { type: 'object', properties: properties } } else { newS.items = { type: 'object' } } newS.originalType = 'record[]' } else { newS.items = { type: 'object' } newS.originalType = 'object[]' } } else { newS.type = 'object' } const preservedFields = [ 'description', 'pattern', 'min', 'max', 'currency', 'currencyLocale', 'multiselect', 'customErrorMessage', 'required', 'showExpr', 'password', 'order', 'dateFormat', 'title', 'placeholder' ] preservedFields.forEach((field) => { // @ts-ignore if (oldS[field] !== undefined) { // @ts-ignore newS[field] = oldS[field] } }) if (oldS.type != newS.type) { for (const prop of Object.getOwnPropertyNames(newS)) { if (prop != 'description') { // @ts-ignore delete oldS[prop] } } } else if ((oldS.format == 'date' || oldS.format === 'date-time') && newS.format == 'string') { newS.format = oldS.format } else if (newS.format == 'date-time' && oldS.format == 'date') { newS.format = 'date' } else if (oldS.items?.type != newS.items?.type) { delete oldS.items } if (oldS.format && !newS.format) { oldS.format = undefined } Object.assign(oldS, newS) }