Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ jobs:

- run: pipx install check-jsonschema

- run: opam exec -- dune fmt

- run: opam exec -- dune runtest
1 change: 1 addition & 0 deletions ppx/browser/dune
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@

; Shared jsonschema deriver sources (see ppx/native/dune for rationale). Compiled
; into melange-json.ppx so [@@deriving jsonschema] works on the Melange ppx too.

(copy_files#
(files ../jsonschema/{attrs,schema,ppx_deriving_jsonschema}.{ml,mli}))
156 changes: 114 additions & 42 deletions ppx/jsonschema/attrs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,121 @@ open Ppxlib

type config = {
variant_as_string : bool;
(** Encode variants as string instead of string array. This option breaks compatibility with yojson derivers and
doesn't support constructors with a payload. *)
(** Encode variants as string instead of string array. This option
breaks compatibility with yojson derivers and doesn't support
constructors with a payload. *)
polymorphic_variant_tuple : bool;
(** Preserve the implicit tuple in a polymorphic variant. This option breaks compatibility with yojson derivers. *)
(** Preserve the implicit tuple in a polymorphic variant. This
option breaks compatibility with yojson derivers. *)
ocaml_doc : bool;
(** Use [ocaml.doc] attributes (i.e. [(** ... *)] comments) as a fallback for [[@jsonschema.description]] when the
explicit annotation is absent. *)
(** Use [ocaml.doc] attributes (i.e. [(** ... *)] comments) as a
fallback for [[@jsonschema.description]] when the explicit
annotation is absent. *)
}

let string_attr name ctx = Attribute.declare name ctx Ast_pattern.(single_expr_payload (estring __')) (fun x -> x)
let string_attr name ctx =
Attribute.declare name ctx
Ast_pattern.(single_expr_payload (estring __'))
(fun x -> x)

let expr_attr name ctx = Attribute.declare name ctx Ast_pattern.(single_expr_payload __) (fun x -> x)
let expr_attr name ctx =
Attribute.declare name ctx
Ast_pattern.(single_expr_payload __)
(fun x -> x)

let jsonschema_key = string_attr "jsonschema.key" Attribute.Context.label_declaration
let jsonschema_ref = string_attr "jsonschema.ref" Attribute.Context.label_declaration
let jsonschema_variant_name = string_attr "jsonschema.name" Attribute.Context.constructor_declaration
let jsonschema_polymorphic_variant_name = string_attr "jsonschema.name" Attribute.Context.rtag
let jsonschema_key =
string_attr "jsonschema.key" Attribute.Context.label_declaration

let jsonschema_ref =
string_attr "jsonschema.ref" Attribute.Context.label_declaration

let jsonschema_variant_name =
string_attr "jsonschema.name" Attribute.Context.constructor_declaration

let jsonschema_polymorphic_variant_name =
string_attr "jsonschema.name" Attribute.Context.rtag

let jsonschema_td_allow_extra_fields =
Attribute.declare "jsonschema.allow_extra_fields" Attribute.Context.type_declaration
Attribute.declare "jsonschema.allow_extra_fields"
Attribute.Context.type_declaration
Ast_pattern.(pstr nil)
(fun () -> ())

let jsonschema_cd_allow_extra_fields =
Attribute.declare "jsonschema.allow_extra_fields" Attribute.Context.constructor_declaration
Attribute.declare "jsonschema.allow_extra_fields"
Attribute.Context.constructor_declaration
Ast_pattern.(pstr nil)
(fun () -> ())

let jsonschema_option = Attribute.declare_flag "jsonschema.option" Attribute.Context.label_declaration
let jsonschema_option =
Attribute.declare_flag "jsonschema.option"
Attribute.Context.label_declaration

let jsonschema_ld_description =
string_attr "jsonschema.description" Attribute.Context.label_declaration

let jsonschema_td_description =
string_attr "jsonschema.description" Attribute.Context.type_declaration

let jsonschema_cd_description =
string_attr "jsonschema.description"
Attribute.Context.constructor_declaration

let jsonschema_ct_description =
string_attr "jsonschema.description" Attribute.Context.core_type

let jsonschema_rtag_description =
string_attr "jsonschema.description" Attribute.Context.rtag

let jsonschema_td_format =
string_attr "jsonschema.format" Attribute.Context.type_declaration

let jsonschema_ld_format =
string_attr "jsonschema.format" Attribute.Context.label_declaration

let jsonschema_ct_format =
string_attr "jsonschema.format" Attribute.Context.core_type

let jsonschema_td_maximum =
expr_attr "jsonschema.maximum" Attribute.Context.type_declaration

let jsonschema_ld_maximum =
expr_attr "jsonschema.maximum" Attribute.Context.label_declaration

let jsonschema_ct_maximum =
expr_attr "jsonschema.maximum" Attribute.Context.core_type

let jsonschema_td_minimum =
expr_attr "jsonschema.minimum" Attribute.Context.type_declaration

let jsonschema_ld_minimum =
expr_attr "jsonschema.minimum" Attribute.Context.label_declaration

let jsonschema_ld_description = string_attr "jsonschema.description" Attribute.Context.label_declaration
let jsonschema_td_description = string_attr "jsonschema.description" Attribute.Context.type_declaration
let jsonschema_cd_description = string_attr "jsonschema.description" Attribute.Context.constructor_declaration
let jsonschema_ct_description = string_attr "jsonschema.description" Attribute.Context.core_type
let jsonschema_rtag_description = string_attr "jsonschema.description" Attribute.Context.rtag
let jsonschema_ct_minimum =
expr_attr "jsonschema.minimum" Attribute.Context.core_type

let jsonschema_td_format = string_attr "jsonschema.format" Attribute.Context.type_declaration
let jsonschema_ld_format = string_attr "jsonschema.format" Attribute.Context.label_declaration
let jsonschema_ct_format = string_attr "jsonschema.format" Attribute.Context.core_type
let jsonschema_ct_attrs =
expr_attr "jsonschema.attrs" Attribute.Context.core_type

let jsonschema_td_maximum = expr_attr "jsonschema.maximum" Attribute.Context.type_declaration
let jsonschema_ld_maximum = expr_attr "jsonschema.maximum" Attribute.Context.label_declaration
let jsonschema_ct_maximum = expr_attr "jsonschema.maximum" Attribute.Context.core_type
let jsonschema_td_attrs =
expr_attr "jsonschema.attrs" Attribute.Context.type_declaration

let jsonschema_td_minimum = expr_attr "jsonschema.minimum" Attribute.Context.type_declaration
let jsonschema_ld_minimum = expr_attr "jsonschema.minimum" Attribute.Context.label_declaration
let jsonschema_ct_minimum = expr_attr "jsonschema.minimum" Attribute.Context.core_type
let jsonschema_ld_attrs =
expr_attr "jsonschema.attrs" Attribute.Context.label_declaration

let jsonschema_ct_attrs = expr_attr "jsonschema.attrs" Attribute.Context.core_type
let jsonschema_td_attrs = expr_attr "jsonschema.attrs" Attribute.Context.type_declaration
let jsonschema_ld_attrs = expr_attr "jsonschema.attrs" Attribute.Context.label_declaration
let jsonschema_ld_default = expr_attr "jsonschema.default" Attribute.Context.label_declaration
let jsonschema_ld_default =
expr_attr "jsonschema.default" Attribute.Context.label_declaration

(* We intentionally do not use [Attribute.get] for [ocaml.doc]/[doc]. These are
compiler-reserved attributes, and [ppxlib] rejects registering them via
[Attribute.declare]. We therefore inspect the raw attribute list directly
with an [Ast_pattern] that matches both the name and the standard string
payload shape in one go. *)
let doc_attr_pattern =
Ast_pattern.(attribute ~name:(string "ocaml.doc" ||| string "doc") ~payload:(single_expr_payload (estring __')))
Ast_pattern.(
attribute
~name:(string "ocaml.doc" ||| string "doc")
~payload:(single_expr_payload (estring __')))

(* A node can carry several [ocaml.doc]/[doc] attributes — e.g. a user writing
one doc comment before a record field and another after. We collect every
Expand All @@ -73,36 +128,48 @@ let find_doc_attr attrs =
(fun attr ->
Ast_pattern.parse_res doc_attr_pattern attr.attr_loc attr Fun.id
|> Result.to_option
|> Option.map (fun ({ txt; loc } : string Location.loc) -> { txt = String.trim txt; loc }))
|> Option.map (fun ({ txt; loc } : string Location.loc) ->
{ txt = String.trim txt; loc }))
attrs
in
match matches with
| [] -> None
| [ single ] -> Some single
| first :: _ as all -> Some { txt = String.concat "\n\n" (List.map (fun x -> x.txt) all); loc = first.loc }
| first :: _ as all ->
Some
{
txt = String.concat "\n\n" (List.map (fun x -> x.txt) all);
loc = first.loc;
}

let fallback_description ~ocaml_doc explicit_desc attrs node =
match Attribute.get explicit_desc node with
| Some _ as x -> x
| None -> if ocaml_doc then find_doc_attr attrs else None

let ld_description ~ocaml_doc (ld : label_declaration) =
fallback_description ~ocaml_doc jsonschema_ld_description ld.pld_attributes ld
fallback_description ~ocaml_doc jsonschema_ld_description
ld.pld_attributes ld

let td_description ~ocaml_doc (td : type_declaration) =
fallback_description ~ocaml_doc jsonschema_td_description td.ptype_attributes td
fallback_description ~ocaml_doc jsonschema_td_description
td.ptype_attributes td

let cd_description ~ocaml_doc (cd : constructor_declaration) =
fallback_description ~ocaml_doc jsonschema_cd_description cd.pcd_attributes cd
fallback_description ~ocaml_doc jsonschema_cd_description
cd.pcd_attributes cd

let ct_description ~ocaml_doc (ct : core_type) =
fallback_description ~ocaml_doc jsonschema_ct_description ct.ptyp_attributes ct
fallback_description ~ocaml_doc jsonschema_ct_description
ct.ptyp_attributes ct

let rtag_description ~ocaml_doc (rf : row_field) =
fallback_description ~ocaml_doc jsonschema_rtag_description rf.prf_attributes rf
fallback_description ~ocaml_doc jsonschema_rtag_description
rf.prf_attributes rf

let jsonschema_td_compact_variants =
Attribute.declare_flag "jsonschema.compact_variants" Attribute.Context.type_declaration
Attribute.declare_flag "jsonschema.compact_variants"
Attribute.Context.type_declaration

let attributes =
[
Expand Down Expand Up @@ -134,4 +201,9 @@ let attributes =
Attribute.T jsonschema_td_compact_variants;
]

let args () = Deriving.Args.(empty +> flag "variant_as_string" +> flag "polymorphic_variant_tuple" +> flag "ocaml_doc")
let args () =
Deriving.Args.(
empty
+> flag "variant_as_string"
+> flag "polymorphic_variant_tuple"
+> flag "ocaml_doc")
147 changes: 106 additions & 41 deletions ppx/jsonschema/attrs.mli
Original file line number Diff line number Diff line change
@@ -1,50 +1,115 @@
type config = {
variant_as_string : bool;
(** Encode variants as string instead of string array. This option breaks compatibility with yojson derivers and
doesn't support constructors with a payload. *)
(** Encode variants as string instead of string array. This option
breaks compatibility with yojson derivers and doesn't support
constructors with a payload. *)
polymorphic_variant_tuple : bool;
(** Preserve the implicit tuple in a polymorphic variant. This option breaks compatibility with yojson derivers. *)
(** Preserve the implicit tuple in a polymorphic variant. This
option breaks compatibility with yojson derivers. *)
ocaml_doc : bool;
(** Use [ocaml.doc] attributes (i.e. [(** ... *)] comments) as a fallback for [[@jsonschema.description]] when the
explicit annotation is absent. *)
(** Use [ocaml.doc] attributes (i.e. [(** ... *)] comments) as a
fallback for [[@jsonschema.description]] when the explicit
annotation is absent. *)
}

val jsonschema_key : (Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_ref : (Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_variant_name : (Ppxlib.constructor_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_polymorphic_variant_name : (Ppxlib.row_field, string Location.loc) Ppxlib.Attribute.t
val jsonschema_td_allow_extra_fields : (Ppxlib.type_declaration, unit -> unit) Ppxlib.Attribute.t
val jsonschema_cd_allow_extra_fields : (Ppxlib.constructor_declaration, unit -> unit) Ppxlib.Attribute.t
val jsonschema_key :
(Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_ref :
(Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_variant_name :
(Ppxlib.constructor_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_polymorphic_variant_name :
(Ppxlib.row_field, string Location.loc) Ppxlib.Attribute.t

val jsonschema_td_allow_extra_fields :
(Ppxlib.type_declaration, unit -> unit) Ppxlib.Attribute.t

val jsonschema_cd_allow_extra_fields :
(Ppxlib.constructor_declaration, unit -> unit) Ppxlib.Attribute.t

val jsonschema_option : Ppxlib.label_declaration Ppxlib.Attribute.flag
val jsonschema_ld_description : (Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_td_description : (Ppxlib.type_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_cd_description : (Ppxlib.constructor_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_ct_description : (Ppxlib.core_type, string Location.loc) Ppxlib.Attribute.t
val jsonschema_rtag_description : (Ppxlib.row_field, string Location.loc) Ppxlib.Attribute.t
val jsonschema_td_format : (Ppxlib.type_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_ld_format : (Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t
val jsonschema_ct_format : (Ppxlib.core_type, string Location.loc) Ppxlib.Attribute.t
val jsonschema_td_maximum : (Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ld_maximum : (Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ct_maximum : (Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_td_minimum : (Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ld_minimum : (Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ct_minimum : (Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ct_attrs : (Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_td_attrs : (Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ld_attrs : (Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t
val jsonschema_ld_default : (Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t

(** [ld_description], [td_description], [cd_description], [ct_description] and [rtag_description] resolve a description
from [[@jsonschema.description "..."]]. When [ocaml_doc] is [true] and the explicit annotation is absent, they fall
back to an [ocaml.doc] attribute (i.e. a [(** ... *)] comment) on the same node. *)
val ld_description : ocaml_doc:bool -> Ppxlib.label_declaration -> string Location.loc option

val td_description : ocaml_doc:bool -> Ppxlib.type_declaration -> string Location.loc option
val cd_description : ocaml_doc:bool -> Ppxlib.constructor_declaration -> string Location.loc option
val ct_description : ocaml_doc:bool -> Ppxlib.core_type -> string Location.loc option
val rtag_description : ocaml_doc:bool -> Ppxlib.row_field -> string Location.loc option

val jsonschema_td_compact_variants : Ppxlib.type_declaration Ppxlib.Attribute.flag

val jsonschema_ld_description :
(Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_td_description :
(Ppxlib.type_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_cd_description :
(Ppxlib.constructor_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_ct_description :
(Ppxlib.core_type, string Location.loc) Ppxlib.Attribute.t

val jsonschema_rtag_description :
(Ppxlib.row_field, string Location.loc) Ppxlib.Attribute.t

val jsonschema_td_format :
(Ppxlib.type_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_ld_format :
(Ppxlib.label_declaration, string Location.loc) Ppxlib.Attribute.t

val jsonschema_ct_format :
(Ppxlib.core_type, string Location.loc) Ppxlib.Attribute.t

val jsonschema_td_maximum :
(Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ld_maximum :
(Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ct_maximum :
(Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_td_minimum :
(Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ld_minimum :
(Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ct_minimum :
(Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ct_attrs :
(Ppxlib.core_type, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_td_attrs :
(Ppxlib.type_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ld_attrs :
(Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val jsonschema_ld_default :
(Ppxlib.label_declaration, Ppxlib.expression) Ppxlib.Attribute.t

val ld_description :
ocaml_doc:bool -> Ppxlib.label_declaration -> string Location.loc option
(** [ld_description], [td_description], [cd_description], [ct_description]
and [rtag_description] resolve a description from
[[@jsonschema.description "..."]]. When [ocaml_doc] is [true] and the
explicit annotation is absent, they fall back to an [ocaml.doc]
attribute (i.e. a [(** ... *)] comment) on the same node. *)

val td_description :
ocaml_doc:bool -> Ppxlib.type_declaration -> string Location.loc option

val cd_description :
ocaml_doc:bool ->
Ppxlib.constructor_declaration ->
string Location.loc option

val ct_description :
ocaml_doc:bool -> Ppxlib.core_type -> string Location.loc option

val rtag_description :
ocaml_doc:bool -> Ppxlib.row_field -> string Location.loc option

val jsonschema_td_compact_variants :
Ppxlib.type_declaration Ppxlib.Attribute.flag

val attributes : Ppxlib.Attribute.packed list
val args : unit -> (bool -> bool -> bool -> 'a, 'a) Ppxlib.Deriving.Args.t
Loading