unr-deaddrop / frontend

DeadDrop's web interface built with Svelte.
0 stars 0 forks source link

Find and test suitable library for rendering forms from JSON schemas #43

Closed lgactna closed 6 months ago

lgactna commented 8 months ago

https://jsonforms.io/ is the generally accepted solution, but it only supports React/Angular/Vue with no known Svelte bindings. Some smaller projects, like https://github.com/webgme/svelte-jsonschema-form and https://github.com/restspace/svelte-schema-form have been developed for Svelte, and there's also more specialized libraries like https://rjsf-team.github.io/react-jsonschema-form/ (for React) and https://jsonform.github.io/jsonform/playground/index.html?example=schema-basic that's entirely client-side, but uses a slightly different structure than

For the libraries that have playgrounds, none of them seem to have support for Pydantic's anyOf field, like so:

{
    "timeout": {
        "anyOf": [
            {
                "type": "integer"
            },
            {
                "type": "null"
            }
        ],
        "default": null,
        "description": "The timeout for the command; returns an empty result on failure.",
        "title": "Timeout"
    }
}

Most libraries don't interpret this as an optional integer field, but instead try and make two options with auto-generated names.

It may be the case that we'll have to either mess with how Pydantic generates these or add some functionality after the fact. We may also just have to deal with it and not use Optional in anything that's going to be used to generate a form.

The cleanest route (allowing nulls and using required where possible) may just be to revalidate every field before it is presented to the frontend. If anyOf is seen, and all elements in the list are just one-element dictionaries with the key type, delete anyOf and replace it with the first non-null type present. For our example, it'd then look like

{
    "timeout": {
        "type": "integer"
        "default": null,
        "description": "The timeout for the command; returns an empty result on failure.",
        "title": "Timeout"
    }
}

This would no longer be correct semantically - after all, null is not an integer. But it fixes our problems without having to invent a new type in Pygin just for the sake of getting forms all the way up in frontend land to work. That said, the schemas are only intended for generating forms in the frontend anyways, so does it really matter?

Assuming that we don't find a library that supports anyOf over types, we have a few options:

Option 1 removes the need for this flaky pre-processing, and if things break, it's really easy to fix. Option 2 preserves more of the typing and allows validation to correctly take place, while still allowing these fields to be absent when passed into BaseModel.model_validate() (since Pydantic will just default to None). This allows other theoretical validators to continue using the "correct" schema, while scoping our monkeypatching to just the frontend.

Pydantic issue 7161 (not linking to avoid visibility) describes this in more detail.

lgactna commented 6 months ago

Effectively done with #61