diff --git a/src/ppx_deriving_jsonschema.ml b/src/ppx_deriving_jsonschema.ml index e0f28da..01604ad 100644 --- a/src/ppx_deriving_jsonschema.ml +++ b/src/ppx_deriving_jsonschema.ml @@ -99,18 +99,29 @@ module Schema = struct enum ~loc (Some "string") values end -let variant_as_array ~loc values = Schema.array_ ~loc ~min_items:1 ~max_items:1 (Schema.enum_string ~loc values) +let variant_as_string ~loc constrs = + Schema.oneOf ~loc + (List.map + (function + | `Tag (name, _typs) -> Schema.const ~loc name + | `Inherit typ -> typ) + constrs) -let variant_as_string ~loc values = Schema.enum_string ~loc values - -let variant_with_payload ~loc constrs = - Schema.oneOf ~loc (List.map (fun (name, typs) -> Schema.tuple ~loc (Schema.const ~loc name :: typs)) constrs) +let variant_as_array ~loc constrs = Schema.array_ ~loc ~min_items:1 ~max_items:1 (variant_as_string ~loc constrs) let variant ~loc ~config values = match config.variant_as_array with | true -> variant_as_array ~loc values | false -> variant_as_string ~loc values +let variant_with_payload ~loc constrs = + Schema.oneOf ~loc + (List.map + (function + | `Tag (name, typs) -> Schema.tuple ~loc (Schema.const ~loc name :: typs) + | `Inherit typ -> typ) + constrs) + let value_name_pattern ~loc type_name = ppat_var ~loc { txt = type_name ^ "_jsonschema"; loc } let create_value ~loc name value = @@ -154,21 +165,17 @@ let rec type_of_core ~loc ~config core_type = | None -> name.txt in let typs = List.map (type_of_core ~loc ~config) typs in - name, typs + `Tag (name, typs) | { prf_desc = Rinherit core_type; _ } -> - (* let typs = [ type_of_core ~loc ~config core_type ] in *) - let name = - Format.asprintf "unsupported polymorphic variant inheritance: %a" Astlib.Pprintast.core_type - core_type (* todo: *) - in - name, []) + let typ = type_of_core ~loc ~config core_type in + `Inherit typ) row_fields in (* todo: raise an error if encoding is as string and constructor has a payload *) let v = match config.variant_as_array with | true -> variant_with_payload ~loc constrs - | false -> variant_as_string ~loc (List.map fst constrs) + | false -> variant_as_string ~loc constrs in v | _ -> @@ -224,17 +231,17 @@ let derive_jsonschema ~ctxt ast variant_as_array = match pcd_args with | Pcstr_record label_declarations -> let typs = [ object_ ~loc ~config label_declarations ] in - name, typs + `Tag (name, typs) | Pcstr_tuple typs -> let types = List.map (type_of_core ~loc ~config) typs in - name, types) + `Tag (name, types)) variants in let v = (* todo: raise an error if encoding is as string and constructor has a payload *) match variant_as_array with | true -> variant_with_payload ~loc variants - | false -> variant_as_string ~loc (List.map fst variants) + | false -> variant_as_string ~loc variants in let jsonschema_expr = create_value ~loc type_name v in [ jsonschema_expr ] diff --git a/test/test.expected.ml b/test/test.expected.ml index 2c41dc3..544f904 100644 --- a/test/test.expected.ml +++ b/test/test.expected.ml @@ -13,8 +13,10 @@ module Mod1 = struct let m_1_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", (`List [`String "A"; `String "B"]))][@@warning "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "A"))]; + `Assoc [("const", (`String "B"))]]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] module Mod2 = struct @@ -25,9 +27,11 @@ module Mod1 = struct let m_2_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", (`List [`String "C"; `String "D"]))][@@warning - "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "C"))]; + `Assoc [("const", (`String "D"))]]))][@@warning + "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] end end @@ -54,10 +58,11 @@ include struct let kind_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", - (`List [`String "Success"; `String "Error"; `String "skipped"]))] - [@@warning "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "Success"))]; + `Assoc [("const", (`String "Error"))]; + `Assoc [("const", (`String "skipped"))]]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema kind_jsonschema type kind_as_array = @@ -98,9 +103,11 @@ include struct let poly_kind_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", (`List [`String "Aaa"; `String "Bbb"; `String "ccc"]))] - [@@warning "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "Aaa"))]; + `Assoc [("const", (`String "Bbb"))]; + `Assoc [("const", (`String "ccc"))]]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema poly_kind_jsonschema type poly_kind_as_array = [ `Aaa | `Bbb | `Ccc [@name "ccc"]][@@deriving @@ -142,9 +149,11 @@ include struct let poly_kind_with_payload_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", (`List [`String "Aaa"; `String "Bbb"; `String "ccc"]))] - [@@warning "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "Aaa"))]; + `Assoc [("const", (`String "Bbb"))]; + `Assoc [("const", (`String "ccc"))]]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema poly_kind_with_payload_jsonschema type poly_kind_with_payload_as_array = @@ -192,19 +201,48 @@ include ("maxItems", (`Int 2))]]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema poly_kind_with_payload_as_array_jsonschema -type poly_inherit = [ `New_one | poly_kind][@@deriving jsonschema] +type poly_inherit = [ `New_one | `Second_one of int | poly_kind][@@deriving + jsonschema] include struct let poly_inherit_jsonschema = `Assoc - [("type", (`String "string")); - ("enum", - (`List - [`String "New_one"; - `String "unsupported polymorphic variant inheritance: poly_kind"]))] - [@@warning "-32-39"] + [("oneOf", + (`List + [`Assoc [("const", (`String "New_one"))]; + `Assoc [("const", (`String "Second_one"))]; + poly_kind_jsonschema]))][@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema poly_inherit_jsonschema +type poly_inherit_as_array = + [ `New_one | `Second_one of int | poly_kind_as_array][@@deriving + jsonschema + ~variant_as_array] +include + struct + let poly_inherit_as_array_jsonschema = + `Assoc + [("oneOf", + (`List + [`Assoc + [("type", (`String "array")); + ("prefixItems", + (`List [`Assoc [("const", (`String "New_one"))]])); + ("unevaluatedItems", (`Bool false)); + ("minItems", (`Int 1)); + ("maxItems", (`Int 1))]; + `Assoc + [("type", (`String "array")); + ("prefixItems", + (`List + [`Assoc [("const", (`String "Second_one"))]; + `Assoc [("type", (`String "integer"))]])); + ("unevaluatedItems", (`Bool false)); + ("minItems", (`Int 2)); + ("maxItems", (`Int 2))]; + poly_kind_as_array_jsonschema]))][@@warning "-32-39"] + end[@@ocaml.doc "@inline"][@@merlin.hide ] +let () = print_schema poly_inherit_as_array_jsonschema type event = { date: float ; @@ -237,9 +275,11 @@ include ("maxLength", (`Int 1))])); ("t", (`Assoc - [("type", (`String "string")); - ("enum", - (`List [`String "Foo"; `String "Bar"; `String "Baz"]))])); + [("oneOf", + (`List + [`Assoc [("const", (`String "Foo"))]; + `Assoc [("const", (`String "Bar"))]; + `Assoc [("const", (`String "Baz"))]]))])); ("l", (`Assoc [("type", (`String "array")); @@ -379,10 +419,29 @@ type 'param2 poly2 = include struct let poly2_jsonschema = - `Assoc [("type", (`String "string")); ("enum", (`List [`String "C"]))] + `Assoc [("oneOf", (`List [`Assoc [("const", (`String "C"))]]))] [@@warning "-32-39"] end[@@ocaml.doc "@inline"][@@merlin.hide ] let () = print_schema poly2_jsonschema +type 'param2 poly2_as_array = + | C of 'param2 [@@deriving jsonschema ~variant_as_array] +include + struct + let poly2_as_array_jsonschema = + `Assoc + [("oneOf", + (`List + [`Assoc + [("type", (`String "array")); + ("prefixItems", + (`List + [`Assoc [("const", (`String "C"))]; + `Assoc [("unsuported core type", (`String "'param2"))]])); + ("unevaluatedItems", (`Bool false)); + ("minItems", (`Int 2)); + ("maxItems", (`Int 2))]]))][@@warning "-32-39"] + end[@@ocaml.doc "@inline"][@@merlin.hide ] +let () = print_schema poly2_as_array_jsonschema type tuple_with_variant = (int * [ `A | `B [@name "second_cstr"]])[@@deriving jsonschema] include @@ -394,8 +453,10 @@ include (`List [`Assoc [("type", (`String "integer"))]; `Assoc - [("type", (`String "string")); - ("enum", (`List [`String "A"; `String "second_cstr"]))]])); + [("oneOf", + (`List + [`Assoc [("const", (`String "A"))]; + `Assoc [("const", (`String "second_cstr"))]]))]])); ("unevaluatedItems", (`Bool false)); ("minItems", (`Int 2)); ("maxItems", (`Int 2))][@@warning "-32-39"] diff --git a/test/test.ml b/test/test.ml index 6718fb9..cd27f18 100644 --- a/test/test.ml +++ b/test/test.ml @@ -81,12 +81,22 @@ let () = print_schema poly_kind_with_payload_as_array_jsonschema type poly_inherit = [ `New_one + | `Second_one of int | poly_kind ] [@@deriving jsonschema] let () = print_schema poly_inherit_jsonschema +type poly_inherit_as_array = + [ `New_one + | `Second_one of int + | poly_kind_as_array + ] +[@@deriving jsonschema ~variant_as_array] + +let () = print_schema poly_inherit_as_array_jsonschema + type event = { date : float; kind_f : kind; @@ -164,6 +174,10 @@ type 'param2 poly2 = C of 'param2 [@@deriving jsonschema] let () = print_schema poly2_jsonschema +type 'param2 poly2_as_array = C of 'param2 [@@deriving jsonschema ~variant_as_array] + +let () = print_schema poly2_as_array_jsonschema + type tuple_with_variant = int * [ `A | `B [@name "second_cstr"] ] [@@deriving jsonschema] let () = print_schema tuple_with_variant_jsonschema diff --git a/test/test_schemas.expected.json b/test/test_schemas.expected.json index 2ef7f85..7ddeacb 100644 --- a/test/test_schemas.expected.json +++ b/test/test_schemas.expected.json @@ -2,15 +2,16 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { - "m2": { "type": "string", "enum": [ "C", "D" ] }, - "m": { "type": "string", "enum": [ "A", "B" ] } + "m2": { "oneOf": [ { "const": "C" }, { "const": "D" } ] }, + "m": { "oneOf": [ { "const": "A" }, { "const": "B" } ] } }, "required": [ "m2", "m" ] } { "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, { "const": "Error" }, { "const": "skipped" } + ] } { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -40,8 +41,7 @@ } { "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "enum": [ "Aaa", "Bbb", "ccc" ] + "oneOf": [ { "const": "Aaa" }, { "const": "Bbb" }, { "const": "ccc" } ] } { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -71,8 +71,7 @@ } { "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "enum": [ "Aaa", "Bbb", "ccc" ] + "oneOf": [ { "const": "Aaa" }, { "const": "Bbb" }, { "const": "ccc" } ] } { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -111,9 +110,56 @@ } { "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "enum": [ - "New_one", "unsupported polymorphic variant inheritance: poly_kind" + "oneOf": [ + { "const": "New_one" }, + { "const": "Second_one" }, + { + "oneOf": [ { "const": "Aaa" }, { "const": "Bbb" }, { "const": "ccc" } ] + } + ] +} +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "array", + "prefixItems": [ { "const": "New_one" } ], + "unevaluatedItems": false, + "minItems": 1, + "maxItems": 1 + }, + { + "type": "array", + "prefixItems": [ { "const": "Second_one" }, { "type": "integer" } ], + "unevaluatedItems": false, + "minItems": 2, + "maxItems": 2 + }, + { + "oneOf": [ + { + "type": "array", + "prefixItems": [ { "const": "Aaa" } ], + "unevaluatedItems": false, + "minItems": 1, + "maxItems": 1 + }, + { + "type": "array", + "prefixItems": [ { "const": "Bbb" } ], + "unevaluatedItems": false, + "minItems": 1, + "maxItems": 1 + }, + { + "type": "array", + "prefixItems": [ { "const": "ccc" } ], + "unevaluatedItems": false, + "minItems": 1, + "maxItems": 1 + } + ] + } ] } { @@ -125,12 +171,18 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, - "kind_f": { "type": "string", "enum": [ "Success", "Error", "skipped" ] }, + "kind_f": { + "oneOf": [ + { "const": "Success" }, { "const": "Error" }, { "const": "skipped" } + ] + }, "date": { "type": "number" } }, "required": [ @@ -149,14 +201,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -179,14 +238,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -209,14 +275,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -245,14 +318,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -282,14 +362,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -318,14 +405,21 @@ "string_ref": { "type": "string" }, "bunch_of_bytes": { "type": "string" }, "c": { "type": "string", "minLength": 1, "maxLength": 1 }, - "t": { "type": "string", "enum": [ "Foo", "Bar", "Baz" ] }, + "t": { + "oneOf": [ + { "const": "Foo" }, { "const": "Bar" }, { "const": "Baz" } + ] + }, "l": { "type": "array", "items": { "type": "string" } }, "a": { "type": "array", "items": { "type": "number" } }, "opt_int": { "type": "integer" }, "comment": { "type": "string" }, "kind_f": { - "type": "string", - "enum": [ "Success", "Error", "skipped" ] + "oneOf": [ + { "const": "Success" }, + { "const": "Error" }, + { "const": "skipped" } + ] }, "date": { "type": "number" } }, @@ -348,7 +442,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", - "properties": { "m": { "type": "string", "enum": [ "A", "B" ] } }, + "properties": { "m": { "oneOf": [ { "const": "A" }, { "const": "B" } ] } }, "required": [ "m" ] } { @@ -359,15 +453,28 @@ } { "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string", - "enum": [ "C" ] + "oneOf": [ { "const": "C" } ] +} +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "array", + "prefixItems": [ + { "const": "C" }, { "unsuported core type": "'param2" } + ], + "unevaluatedItems": false, + "minItems": 2, + "maxItems": 2 + } + ] } { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "array", "prefixItems": [ { "type": "integer" }, - { "type": "string", "enum": [ "A", "second_cstr" ] } + { "oneOf": [ { "const": "A" }, { "const": "second_cstr" } ] } ], "unevaluatedItems": false, "minItems": 2,