Skip to content

Commit

Permalink
feat: add literal object type support (#401)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenfiszel committed Aug 13, 2022
1 parent 0384727 commit 845de82
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 52 deletions.
54 changes: 51 additions & 3 deletions backend/openapi.yaml
Expand Up @@ -3646,7 +3646,6 @@ components:
resource:
type: string
nullable: true

required:
- resource
- type: object
Expand All @@ -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
Expand Down
13 changes: 5 additions & 8 deletions backend/src/parser.rs
Expand Up @@ -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)]
Expand All @@ -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,
}

Expand Down
6 changes: 3 additions & 3 deletions backend/src/parser_py.rs
Expand Up @@ -15,7 +15,7 @@ use serde_json::json;

use crate::{
error,
parser::{Arg, InnerTyp, MainArgSignature, Typ},
parser::{Arg, MainArgSignature, Typ},
};

use rustpython_parser::{
Expand Down Expand Up @@ -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,
Expand Down
57 changes: 33 additions & 24 deletions backend/src/parser_ts.rs
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand All @@ -126,25 +127,32 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
},
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, .. }),
TsType::TsTypeLit(TsTypeLit { members, .. }) => {
let properties = members
.into_iter()
.filter_map(|x| match x {
TsTypeElement::TsPropertySignature(TsPropertySignature {
key,
type_ann,
..
}) => 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),
}) => 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,
},
//TsType::TsKeywordType(())
_ => Typ::List(InnerTyp::Str),
},
false,
)
_ => None,
})
.collect();
(Typ::Object(properties), false)
}
// TODO: we can do better here and extract the inner type of array
TsType::TsArrayType(TsArrayType { elem_type, .. }) => {
(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)
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/common.ts
Expand Up @@ -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 = {
Expand Down
32 changes: 22 additions & 10 deletions frontend/src/lib/components/ArgInput.svelte
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/InputTransformForm.svelte
Expand Up @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/ResourceEditor.svelte
Expand Up @@ -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">
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/lib/components/SchemaForm.svelte
Expand Up @@ -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]
}
Expand Down Expand Up @@ -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}
/>
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/lib/infer.ts
Expand Up @@ -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)) {
Expand All @@ -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) {
Expand Down

0 comments on commit 845de82

Please sign in to comment.