mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
feat: add literal object type support (#401)
This commit is contained in:
+51
-3
@@ -3646,7 +3646,6 @@ components:
|
||||
resource:
|
||||
type: string
|
||||
nullable: true
|
||||
|
||||
required:
|
||||
- resource
|
||||
- type: object
|
||||
@@ -3658,11 +3657,60 @@ components:
|
||||
nullable: true
|
||||
required:
|
||||
- str
|
||||
- type: object
|
||||
properties:
|
||||
object:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
typ:
|
||||
oneOf:
|
||||
- type: string
|
||||
enum:
|
||||
[
|
||||
"float",
|
||||
"int",
|
||||
"bool",
|
||||
"email",
|
||||
"unknown",
|
||||
"bytes",
|
||||
"dict",
|
||||
"datetime",
|
||||
"sql",
|
||||
]
|
||||
- type: object
|
||||
properties:
|
||||
str: {}
|
||||
required: [str]
|
||||
required:
|
||||
- key
|
||||
- typ
|
||||
required:
|
||||
- object
|
||||
- type: object
|
||||
properties:
|
||||
list:
|
||||
type: string
|
||||
enum: ["str", "float", "int", "email"]
|
||||
oneOf:
|
||||
- type: string
|
||||
enum:
|
||||
[
|
||||
"float",
|
||||
"int",
|
||||
"bool",
|
||||
"email",
|
||||
"unknown",
|
||||
"bytes",
|
||||
"dict",
|
||||
"datetime",
|
||||
"sql",
|
||||
]
|
||||
- type: object
|
||||
properties:
|
||||
str: {}
|
||||
required: [str]
|
||||
nullable: true
|
||||
required:
|
||||
- list
|
||||
|
||||
@@ -17,12 +17,9 @@ pub struct MainArgSignature {
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all(serialize = "lowercase"))]
|
||||
pub enum InnerTyp {
|
||||
Str,
|
||||
Int,
|
||||
Float,
|
||||
Bytes,
|
||||
Email,
|
||||
pub struct ObjectProperty {
|
||||
pub key: String,
|
||||
pub typ: Box<Typ>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
@@ -32,13 +29,13 @@ pub enum Typ {
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
Dict,
|
||||
List(InnerTyp),
|
||||
List(Box<Typ>),
|
||||
Bytes,
|
||||
Datetime,
|
||||
Resource(String),
|
||||
Email,
|
||||
Sql,
|
||||
Object(Vec<ObjectProperty>),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
error,
|
||||
parser::{Arg, InnerTyp, MainArgSignature, Typ},
|
||||
parser::{Arg, MainArgSignature, Typ},
|
||||
};
|
||||
|
||||
use rustpython_parser::{
|
||||
@@ -67,8 +67,8 @@ pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
"float" => Typ::Float,
|
||||
"int" => Typ::Int,
|
||||
"bool" => Typ::Bool,
|
||||
"dict" => Typ::Dict,
|
||||
"list" => Typ::List(InnerTyp::Str),
|
||||
"dict" => Typ::Object(vec![]),
|
||||
"list" => Typ::List(Box::new(Typ::Str(None))),
|
||||
"bytes" => Typ::Bytes,
|
||||
"datetime" => Typ::Datetime,
|
||||
"datetime.datetime" => Typ::Datetime,
|
||||
|
||||
+33
-24
@@ -8,14 +8,15 @@
|
||||
|
||||
use crate::{
|
||||
error,
|
||||
parser::{Arg, InnerTyp, MainArgSignature, Typ},
|
||||
parser::{Arg, MainArgSignature, ObjectProperty, Typ},
|
||||
};
|
||||
|
||||
use swc_common::{sync::Lrc, FileName, SourceMap};
|
||||
use swc_ecma_ast::{
|
||||
AssignPat, BindingIdent, Decl, ExportDecl, FnDecl, Ident, ModuleDecl, ModuleItem, Pat, Str,
|
||||
TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType,
|
||||
TsType, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
|
||||
AssignPat, BindingIdent, Decl, ExportDecl, Expr, FnDecl, Ident, ModuleDecl, ModuleItem, Pat,
|
||||
Str, TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType,
|
||||
TsOptionalType, TsPropertySignature, TsType, TsTypeElement, TsTypeLit, TsTypeRef,
|
||||
TsUnionOrIntersectionType, TsUnionType,
|
||||
};
|
||||
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
|
||||
|
||||
@@ -113,11 +114,11 @@ fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String
|
||||
}
|
||||
|
||||
fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
|
||||
//println!("{:?}", ts_type);
|
||||
println!("{:?}", ts_type);
|
||||
match ts_type {
|
||||
TsType::TsKeywordType(t) => (
|
||||
match t.kind {
|
||||
TsKeywordTypeKind::TsObjectKeyword => Typ::Dict,
|
||||
TsKeywordTypeKind::TsObjectKeyword => Typ::Object(vec![]),
|
||||
TsKeywordTypeKind::TsBooleanKeyword => Typ::Bool,
|
||||
TsKeywordTypeKind::TsBigIntKeyword => Typ::Int,
|
||||
TsKeywordTypeKind::TsNumberKeyword => Typ::Float,
|
||||
@@ -126,25 +127,32 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
|
||||
},
|
||||
false,
|
||||
),
|
||||
TsType::TsTypeLit(TsTypeLit { members, .. }) => {
|
||||
let properties = members
|
||||
.into_iter()
|
||||
.filter_map(|x| match x {
|
||||
TsTypeElement::TsPropertySignature(TsPropertySignature {
|
||||
key,
|
||||
type_ann,
|
||||
..
|
||||
}) => match (*key.to_owned(), type_ann) {
|
||||
(Expr::Ident(Ident { sym, .. }), type_ann) => Some(ObjectProperty {
|
||||
key: sym.to_string(),
|
||||
typ: type_ann
|
||||
.as_ref()
|
||||
.map(|typ| Box::new(tstype_to_typ(&*typ.type_ann).0))
|
||||
.unwrap_or(Box::new(Typ::Unknown)),
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
(Typ::Object(properties), false)
|
||||
}
|
||||
// TODO: we can do better here and extract the inner type of array
|
||||
TsType::TsArrayType(TsArrayType { elem_type, .. }) => {
|
||||
(
|
||||
match &**elem_type {
|
||||
TsType::TsTypeRef(TsTypeRef {
|
||||
type_name: TsEntityName::Ident(Ident { sym, .. }),
|
||||
..
|
||||
}) => match sym.to_string().as_str() {
|
||||
"Base64" => Typ::List(InnerTyp::Bytes),
|
||||
"Email" => Typ::List(InnerTyp::Email),
|
||||
"bigint" => Typ::List(InnerTyp::Int),
|
||||
"number" => Typ::List(InnerTyp::Float),
|
||||
_ => Typ::List(InnerTyp::Str),
|
||||
},
|
||||
//TsType::TsKeywordType(())
|
||||
_ => Typ::List(InnerTyp::Str),
|
||||
},
|
||||
false,
|
||||
)
|
||||
(Typ::List(Box::new(tstype_to_typ(&**elem_type).0)), false)
|
||||
}
|
||||
TsType::TsLitType(TsLitType { lit: TsLit::Str(Str { value, .. }), .. }) => {
|
||||
(Typ::Str(Some(vec![value.to_string()])), false)
|
||||
@@ -232,7 +240,8 @@ mod tests {
|
||||
export function main(test1?: string, test2: string = \"burkina\",
|
||||
test3: wmill.Resource<'postgres'>, b64: Base64, ls: Base64[],
|
||||
email: Email, literal: \"test\", literal_union: \"test\" | \"test2\",
|
||||
opt_type?: string | null, opt_type_union: string | null, opt_type_union_union2: string | undefined) {
|
||||
opt_type?: string | null, opt_type_union: string | null, opt_type_union_union2: string | undefined,
|
||||
min_object: {a: string, b: number}) {
|
||||
console.log(42)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface SchemaProperty {
|
||||
contentEncoding?: 'base64' | 'binary'
|
||||
format?: string
|
||||
items?: { type?: 'string' | 'number' | 'bytes', contentEncoding?: 'base64' },
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
|
||||
export type Schema = {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import type { Schema, SchemaProperty } from '$lib/common'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
@@ -41,6 +43,7 @@
|
||||
| { type?: 'string' | 'number' | 'bytes'; contentEncoding?: 'base64' }
|
||||
| undefined = undefined
|
||||
export let displayHeader = true
|
||||
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
|
||||
|
||||
let seeEditable: boolean = enum_ != undefined || pattern != undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -269,16 +272,25 @@
|
||||
{:else if inputCat == 'resource-object'}
|
||||
<ObjectResourceInput {format} bind:value />
|
||||
{:else if inputCat == 'object'}
|
||||
<textarea
|
||||
{disabled}
|
||||
style="min-height: {minHeight}; max-height: {maxHeight}"
|
||||
on:input={async () => recomputeRowSize(rawValue ?? '')}
|
||||
class="col-span-10 {valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
|
||||
placeholder={defaultValue ? JSON.stringify(defaultValue, null, 4) : ''}
|
||||
bind:value={rawValue}
|
||||
/>
|
||||
{#if properties}
|
||||
<div class="p-4 pl-8 border rounded w-full">
|
||||
<SchemaForm
|
||||
schema={{ properties, $schema: '', required: [], type: 'object' }}
|
||||
bind:args={value}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<textarea
|
||||
{disabled}
|
||||
style="min-height: {minHeight}; max-height: {maxHeight}"
|
||||
on:input={async () => recomputeRowSize(rawValue ?? '')}
|
||||
class="col-span-10 {valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
|
||||
placeholder={defaultValue ? JSON.stringify(defaultValue, null, 4) : ''}
|
||||
bind:value={rawValue}
|
||||
/>
|
||||
{/if}
|
||||
{:else if inputCat == 'enum'}
|
||||
<select {disabled} class="px-6" bind:value>
|
||||
{#each enum_ ?? [] as e}
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
properties={schema.properties[argName].properties}
|
||||
displayHeader={false}
|
||||
bind:inputCat={inputCats[argName]}
|
||||
on:input={(e) => {
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
enum_={resourceSchema.properties[fieldName]?.enum}
|
||||
contentEncoding={resourceSchema.properties[fieldName]?.contentEncoding}
|
||||
itemsType={resourceSchema.properties[fieldName]?.items}
|
||||
properties={resourceSchema.properties[fieldName]?.properties}
|
||||
format={resourceSchema.properties[fieldName]?.format}
|
||||
/>
|
||||
<div class="pb-3 ml-2 relative">
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
|
||||
$: if (!args) {
|
||||
args = {}
|
||||
}
|
||||
|
||||
function removeExtraKey() {
|
||||
Object.keys(args).forEach((key) => {
|
||||
Object.keys(args ?? {}).forEach((key) => {
|
||||
if (!Object.keys(schema?.properties ?? {}).includes(key)) {
|
||||
delete args[key]
|
||||
}
|
||||
@@ -54,6 +58,7 @@
|
||||
bind:enum_={schema.properties[argName].enum}
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
properties={schema.properties[argName].properties}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
{editableSchema}
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,11 @@ export async function inferArgs(
|
||||
}
|
||||
|
||||
function argSigToJsonSchemaType(
|
||||
t: string | { resource: string | null } | { list: string | null } | { str: string[] | null },
|
||||
t: string
|
||||
| { resource: string | null }
|
||||
| { list: string | { str: any } | null }
|
||||
| { str: string[] | null }
|
||||
| { object: { key: string, typ: any }[] },
|
||||
s: SchemaProperty
|
||||
): void {
|
||||
for (const prop of Object.getOwnPropertyNames(s)) {
|
||||
@@ -60,14 +64,22 @@ function argSigToJsonSchemaType(
|
||||
} else if (t === 'sql') {
|
||||
s.type = 'string'
|
||||
s.format = 'sql'
|
||||
} else if (t === 'dict') {
|
||||
s.type = 'object'
|
||||
} else if (t === 'bytes') {
|
||||
s.type = 'string'
|
||||
s.contentEncoding = 'base64'
|
||||
} else if (t === 'datetime') {
|
||||
s.type = 'string'
|
||||
s.format = 'date-time'
|
||||
} else if (typeof t !== 'string' && `object` in t) {
|
||||
s.type = 'object'
|
||||
if (t.object) {
|
||||
const properties = {}
|
||||
for (const prop of t.object) {
|
||||
properties[prop.key] = {}
|
||||
argSigToJsonSchemaType(prop.typ, properties[prop.key])
|
||||
}
|
||||
s.properties = properties
|
||||
}
|
||||
} else if (typeof t !== 'string' && `str` in t) {
|
||||
s.type = 'string'
|
||||
if (t.str) {
|
||||
|
||||
Reference in New Issue
Block a user