jsontypedef / json-typedef-codegen

A CLI tool that generates code from JSON Typedef schemas
https://jsontypedef.com/docs/tools/jtd-codegen
MIT License
160 stars 31 forks source link

Panic on empty discriminator mapping #20

Open karvus opened 3 years ago

karvus commented 3 years ago

A minimal example is as follows:

{
    "properties": {
        "tag": {
            "enum": [
                "foo"
            ]
        },
        "bar": {
            "discriminator": "tag",
            "mapping": {
                "foo": {}
            }
        }
    }
}

As I understand the spec, this should be a valid Typedef. When I try to run it by

$ RUST_BACKTRACE=full jtd-codegen foo.jtd.json --log-format minimal --typescript-out .

I get the following panic and backtrace.

TypeScript: writing to: .
thread 'main' panicked at 'internal error: entered unreachable code', /project/crates/core/src/codegen/ast.rs:377:30
stack backtrace:
   0:           0x73717b - std::backtrace_rs::backtrace::dbghelp::trace::h61b20ba2180b2411
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98:5
   1:           0x73717b - std::backtrace_rs::backtrace::trace_unsynchronized::h83cb15607b6aabb2        
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66:5
   2:           0x73717b - std::sys_common::backtrace::_print_fmt::h1db737bace97a9a3
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\sys_common\backtrace.rs:67:5
   3:           0x73717b - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h34d7951d049896a6
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\sys_common\backtrace.rs:46:22
   4:           0x7a2a9b - core::fmt::write::h869094fcfa18bcd2
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\core\src\fmt\mod.rs:1078:17
   5:           0x7286e1 - std::io::Write::write_fmt::h09af653157e460eb
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\io\mod.rs:1517:15
   6:           0x73af08 - std::sys_common::backtrace::_print::h1e43b04f309c2b98
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\sys_common\backtrace.rs:49:5
   7:           0x73af08 - std::sys_common::backtrace::print::hf10619d2eb24b971
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\sys_common\backtrace.rs:36:9
   8:           0x73af08 - std::panicking::default_hook::{{closure}}::hba2be3522cb11021
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:208:50
   9:           0x73aa2d - std::panicking::default_hook::hae3332b244cce04c
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:225:9
  10:           0x73b789 - std::panicking::rust_panic_with_hook::h591ddcf8b9c5a5c0
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:591:17
  11:           0x73b2f8 - std::panicking::begin_panic_handler::{{closure}}::hdeb48e5ef966eb2f
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:495:13
  12:           0x737b2f - std::sys_common::backtrace::__rust_end_short_backtrace::h30bf946dcb572b09    
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\sys_common\backtrace.rs:141:18
  13:           0x73b289 - rust_begin_unwind
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:493:5
  14:           0x79eb40 - core::panicking::panic_fmt::h2a0fa684de22a764
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\core\src\panicking.rs:92:14
  15:           0x79ea8c - core::panicking::panic::h731c7b783f4684bb
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\core\src\panicking.rs:50:5
  16:           0x44fdf2 - jtd_codegen::codegen::ast::Ast::new::h4bf1d5cbf223dbd0
  17:           0x44e5f2 - jtd_codegen::codegen::ast::Ast::new::h4bf1d5cbf223dbd0
  18:           0x44ad20 - jtd_codegen::codegen::ast::Ast::new_top_level::hd15b14dd193b5373
  19:           0x45f87b - jtd_codegen::codegen::ast::SchemaAst::new::hb66fafe858a2aa60
  20:           0x434388 - jtd_codegen::codegen::codegen::h823d9544d601a5f1
  21:           0x473e90 - jtd_codegen::main::hafe47fad1009e448
  22:           0x43d066 - std::sys_common::backtrace::__rust_begin_short_backtrace::hf52320e7b3bae508  
  23:           0x43d0bd - std::rt::lang_start::{{closure}}::h71c4337b36217baa
  24:           0x73ba0c - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h4082d3e330be1195
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\library\core\src\ops/function.rs:259:13
  25:           0x73ba0c - std::panicking::try::do_call::h780e3a0d8897887c
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:379:40
  26:           0x73ba0c - std::panicking::try::h2567bf242b4fa6c6
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:343:19
  27:           0x73ba0c - std::panic::catch_unwind::h494a7b02b9feffd3
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panic.rs:396:14
  28:           0x73ba0c - std::rt::lang_start_internal::he12bde4ee8419433
                               at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\rt.rs:51:25
  29:           0x475848 - main
  30:           0x4013f8 - __tmainCRTStartup
                               at /./mingw-w64-crt/crt/crtexe.c:334
  31:           0x40151b - mainCRTStartup
                               at /./mingw-w64-crt/crt/crtexe.c:212
  32:     0x7ffb3aab7034 - <unknown>
  33:     0x7ffb3bc22651 - <unknown>

Version is 0.4.0.

$ jtd-codegen --version
jtd-codegen 0.4.0

I feel that this is a bug.

Cheers

Edit: I tried with the latest release 0.4.1, and the problem remains the same.

ucarion commented 3 years ago

Hi, thanks for taking the time to report this bug report!

This is indeed a bug. The schema you provided is in fact not a valid JSON Type Definition schema -- the schema at /properties/bar/mapping/foo is of the empty form, but mapping schemas must be of the properties form -- but jtd-codegen should be providing a clear error message to this effect, and certainly shouldn't be panicking.

A bit more details on what schemas can appear inside mappings is covered here: https://jsontypedef.com/docs/jtd-in-5-minutes/

You can only use properties / optionalProperties / additionalProperties in the schemas you put directly in mapping. You can’t use any other kind of schema, otherwise things would become ambiguous.

Admittedly, that language is a bit loose, and I understand how you would think that using no keywords at all would be kosher. The RFC is a bit more explicit: https://tools.ietf.org/html/rfc8927#section-2

   ; discriminator describes the "discriminator" schema form.
   ;
   ; There are additional constraints on this form that cannot be
   ; expressed in CDDL. Section 2.2.8 describes these additional
   ; constraints in detail.
   discriminator = (
     discriminator: tstr,
     ; Note well: this rule is defined in terms of the "properties"
     ; CDDL rule, not the "schema" CDDL rule.
     mapping: { * tstr => { properties } }
     shared,
   )

But again: jtd-codegen should not be leaving you out to dry like this.

In the immediate term, I suspect you'll have success if you alter your schema to be a valid JTD schema. For instance, just adding "properties": {} to foo would make your schema valid. This schema seems to work, at least for me:

{
    "properties": {
        "tag": {
            "enum": [
                "foo"
            ]
        },
        "bar": {
            "discriminator": "tag",
            "mapping": {
                "foo": {
                    "properties": {}
                }
            }
        }
    }
}

In the longer term, the jtd-codegen bug here is that the tool should validate a schema's correctness before attempting to generate code from it, and produce an error message rather than generate code (and ultimately crash).

karvus commented 3 years ago

Thank you for the prompt reply, and for taking the time to explain!