JSON schema and parsing
Windmill leverages the JSON Schema to define the structure and validation rules for JSON data, adhering to the JSON Schema standard (version 2020-12) for schema definition.
JSON Schema specification
A JSON Schema defines the properties, types, and constraints of a JSON object. It consists of the following components:
$schema
: The URL that points to the JSON Schema specification.type
: The type of the JSON object (e.g., object, string, number).properties
: A dictionary that defines the properties of the JSON object along with their descriptions and types.required
: A list of the mandatory properties.- Additional features and constraints can be added to the JSON Schema, such as validation against regular expressions or other custom formats.
JSON Schema in Windmill
In Windmill, the JSON Schema is used in various contexts, such as defining the input specification for scripts and flows, and specifying resource types.
Below is a simplified spec of a JSON Schema. See here for its full spec. Windmill is compatible with the 2020-12 version. It is not compatible with its most advanced features yet.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"your_name": {
"description": "The name to hello world to",
"type": "string"
},
"your_nickname": {
"description": "If you prefer a nickname, that's fine too",
"type": "string"
}
},
"required": []
}
Where the properties
field contains a dictionary of arguments, and required
is the list of all the mandatory arguments.
The property names need to match the arguments declared by the main function, in our example your_name
and your_nickname
. There is a lot you can do with arguments, types, and validation, but to keep it short:
- Arguments can specify a type
integer
,number
,string
,boolean
,object
,array
orany
. The user's input will be validated against that type. - One can further constraint the type by having the string following a RegEx or pattern, or the object to be of a specific Resource Type.
- Arguments can be made mandatory by adding them to the
required
list. In that case, the generated UI will check that user input provides required arguments. - Each argument can have a description and default fields, that will appear in the generated UI.
- Some types have advanced settings.
Script parameters to JSON Schema
Scripts in Windmill have input parameters defined by a JSON Schema, where each parameter in the main function of a script corresponds to a field in the JSON Schema. This one-to-one correspondence ensures that the name of the argument becomes the name of the property, and most primitive types in Python and TypeScript have a corresponding primitive type in JSON and JSON Schema. During script execution, the parameters and their types are validated against the JSON Schema, ensuring that the input adheres to the expected format.
In Python:
Python | JSON Schema |
---|---|
str | string |
float | number |
Literal["a", "b"] | string with enums: "a", "b" |
int | integer |
bool | boolean |
dict | object |
list | any[] |
List[str] | string[] |
bytes | string, encodingFormat: base64 |
datetime | str, format: date-time |
_ | any |
DynSelect_foo | dynselect-<name> |
TypeScript | JSON Schema |
---|---|
string | string |
"a" | "b" | string with enums: "a", "b" |
object | object |
boolean | boolean |
bigint | int |
number | number |
string[] | string[] |
("foo" | "bar")[] | enum[] |
oneOf | object |
DynSelect_foo | dynselect-<name> |
However in TypeScript there also some special types that are specific to Windmill. They are as follows:
Windmill | JSON Schema |
---|---|
wmill.Base64 | string , encoding$$Format: base64 |
wmill.Email | string , format: email |
wmill.Sql | string , format: sql |
<ResourceType> | object , format: resource-{resource_type} |
The <ResourceType>
is any type that has a matching resource_type in the workspace (more details here). Note that the CamelCase of the type is converted to the snake_case.
Base64
and Email
are actually a type alias for string
, and Resource
is a
type alias for an object
. They are purely type hints for the Windmill parser.
The sql
format is specific to Windmill and replaces the normal text field with
a monaco editor with SQL support.
The equivalent of the type Postgresql
in Python is the
following:
my_resource_type = dict
def main(x: my_resource_type):
...
The JSON Schema of a script's arguments is visible and can be modified from the Generated UI
menu.
Flows parameters and JSON Schema
Flows in Windmill have input parameters defined by a JSON Schema. Each argument of the Flow Input
section corresponds to a field in the JSON Schema. The parameters and their types are validated against the JSON Schema during flow execution.
The JSON Schema of a script's arguments can be modified in the Flow Input
menu. The schema is visible from the Export / OpenFlow
section, in particular the "Input Schema" tab.
Inline scripts of flows & apps use an autogenerated JSON Schema that is implicitly used by the frontend.
Resource types and JSON Schema
Resource types in Windmill are associated with JSON Schemas. A resource type defines the structure and constraints of a resource object. JSON Schema is used to validate the properties and values of a resource object against the specified resource type.
Advanced settings
main function's arguments can be given advanced settings that will affect the inputs' auto-generated UI and JSON Schema.
Here is an example on how to define a Python list as an enum of strings using the Generated UI
menu.
Each argument has the following settings:
- Name: the name of the argument (defined in the main Function).
- Type: the type of the argument (defined in the main Function): Integer, Number, String, Boolean, Array, Object, or Any.
- Description: the description of the argument.
- Custom Title: will be displayed in the UI instead of the field name.
- Placeholder: will be displayed in the input field when the field is empty. If not set, the default value (directly set from the script code) will be used. The placeholder is disabled depending on the field type, format, etc.
- Field settings: advanced settings depending on the type of the field.
Below is the list of advanced settings for each type of field:
Type | Advanced Configuration |
---|---|
Integer | Min and Max. Currency. Currency locale. |
Number | Min and Max. Currency. Currency locale. |
String | Min textarea rows. Disable variable picker. Is Password (will create a variable when filled). Field settings: - File (base64) | Enum | Format: email, hostname, uri, uuid, ipv4, yaml, sql, date-time | Pattern (Regex) |
Boolean | No advanced configuration for this type. |
Object | Advanced settings are Resource types. |
Array | - Items are strings | Items are strings from an enum | Items are objects (JSON) | Items are numbers | Items are bytes |
Any | No advanced configuration for this type. |
Special types
There are special types that lead to a custom behavior in the auto-generated UIs.
oneOf
oneOf
is a type that can be used to have the user pick between two objects through auto-generated UI. Within a TypeScript, it can be used with the following syntax:
example_oneof: { label: "Option 1", attribute: string } | { label: "Option 2", other_attribute: string }
And the auto-generated UI will render:
For flows, oneOf
can be added as a flow input.
The result of the user's selection will be the selected object. With the example above if user picks Option 2 and enters a custom value:
{
"label": "Option 2",
"other_attribute": "Value that the user entered"
}
oneOf
input can also be filled with CLI or webhooks, for example with wmill
CLI:
wmill script run u/user/path -d '{"example_oneof":{"label":"Option 2","attribute":"Value that the user entered"}}'
Dynamic select
Dynamic select is an helper function within scripts that allows you to create a select field with dynamic options.
You must export the string type as DynSelect_<name>
and the function as <name>
:
- TypeScript
- Python
export type DynSelect_foo = string
export async function foo(x: string, y: number) {
if (x === "bar") {
return [{ value: "barbar", label: "barbarbar" }];
}
return [
{ value: '1', label: 'Foo' + x + y },
{ value: '2', label: 'Bar' },
{ value: '3', label: 'Foobar' }
]
}
export async function main(y: number, x: string, xy: DynSelect_foo) {
console.log(xy)
return xy
}
DynSelect_foo = str
def foo(x: str, y: int):
if x == "bar":
return [{"value": "barbar", "label": "barbarbar"}]
return [
{ "value": '1', "label": 'Foo' + x + str(y) },
{ "value": '2', "label": 'Bar' },
{ "value": '3', "label": 'Foobar' }
]
def main(x: str, y: int, xy: DynSelect_foo):
print(xy)
return xy
The select options recompute dynamically based on the other arguments, in this case x
and y
.
You can also do your own filtering and sorting of the options. In the example above, if x
is "bar", the options will be filtered to only one option.