Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow <json repr="string"> for int types #330

Merged
merged 4 commits into from
Mar 17, 2023
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
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
2.12.0 (xxxx-xx-xx)
-------------------

* atdgen: Annotate generated code with types to disambiguate OCaml
classic variants (#331)
Expand All @@ -8,6 +9,8 @@
however, is still a nullable (`Optional`) to make things simpler for
Python programmers. This prevents distinguishing `["Some", "None"]`
from `"None"` which both translate to `None` in Python. (#332)
* (BREAKING) atdgen: revert default encoding of int64 values as string (#330)
* atdgen: Support `<json repr="string">` for `int` values (#330)

2.11.0 (2023-02-08)
-------------------
Expand Down
24 changes: 23 additions & 1 deletion atd/src/json.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Mapping from ATD to JSON
*)

type json_int =
| Int
| String

type json_float =
| Float of int option (* max decimal places *)
| Int
Expand Down Expand Up @@ -57,7 +61,7 @@ type json_repr =
| External
| Field of json_field
| Float of json_float
| Int
| Int of json_int
| List of json_list
| Nullable
| Option
Expand Down Expand Up @@ -107,6 +111,12 @@ let json_float_of_string s : [ `Float | `Int ] option =
| "int" -> Some `Int
| _ -> None

let json_int_of_string s : [ `String | `Int ] option =
match s with
"int" -> Some `Int
| "string" -> Some `String
| _ -> None

let json_precision_of_string s =
try Some (int_of_string s)
with _ -> None
Expand All @@ -130,6 +140,18 @@ let get_json_float an : json_float =
`Float -> Float (get_json_precision an)
| `Int -> Int

let get_json_int an : json_int =
match
Annot.get_field
~parse:json_int_of_string
~default:`Int
~sections:["json"]
~field:"repr"
an
with
`Int -> Int
| `String -> String

let json_list_of_string s : json_list option =
match s with
| "array" -> Some Array
Expand Down
8 changes: 7 additions & 1 deletion atd/src/json.mli
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ type json_adapter = {

val no_adapter : json_adapter

type json_int =
| Int
| String

type json_float =
| Float of int option (* max decimal places *)
| Int
Expand Down Expand Up @@ -52,7 +56,7 @@ type json_repr =
| External
| Field of json_field
| Float of json_float
| Int
| Int of json_int
| List of json_list
| Nullable
| Option
Expand All @@ -70,6 +74,8 @@ val get_json_list : Annot.t -> json_list

val get_json_float : Annot.t -> json_float

val get_json_int : Annot.t -> json_int

val get_json_cons : string -> Annot.t -> string

val get_json_fname : string -> Annot.t -> string
Expand Down
25 changes: 24 additions & 1 deletion atdgen-runtime/src/oj_run.ml
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,33 @@ let write_nullable write_item ob = function
None -> Buffer.add_string ob "null"
| Some x -> write_item ob x

let write_int_as_string ob x =
Buffer.add_char ob '"';
Yojson.Safe.write_int ob x;
Buffer.add_char ob '"'

let write_int8 ob x =
Yojson.Safe.write_int ob (int_of_char x)

let write_int8_as_string ob x =
Buffer.add_char ob '"';
write_int8 ob x;
Buffer.add_char ob '"'

let write_int32 ob x =
Buffer.add_string ob (Int32.to_string x)

let write_int32_as_string ob x =
Buffer.add_char ob '"';
write_int32 ob x;
Buffer.add_char ob '"'

let write_int64 ob x =
Buffer.add_string ob (Int64.to_string x)

let write_int64_as_string ob x =
Buffer.add_char ob '"';
Buffer.add_string ob (Int64.to_string x);
write_int64 ob x;
Buffer.add_char ob '"'

let min_float = float min_int
Expand All @@ -124,6 +142,11 @@ let write_float_as_int ob x =
| FP_infinite -> error "Cannot convert inf or -inf into a JSON int"
| FP_nan -> error "Cannot convert NaN into a JSON int"

let write_float_as_int_string ob x =
Buffer.add_char ob '"';
write_float_as_int ob x;
Buffer.add_char ob '"'

type 'a read = Yojson.lexer_state -> Lexing.lexbuf -> 'a

let read_null p lb =
Expand Down
5 changes: 5 additions & 0 deletions atdgen-runtime/src/oj_run.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ val error : string -> _
val write_list : 'a write -> 'a list write
val write_array : 'a write -> 'a array write
val write_float_as_int : float write
val write_float_as_int_string : float write
val write_assoc_list : 'a write -> 'b write -> ('a * 'b) list write
val write_assoc_array : 'a write -> 'b write -> ('a * 'b) array write
val write_option : 'a write -> 'a option write
val write_std_option : 'a write -> 'a option write
val write_nullable : 'a write -> 'a option write
val write_int_as_string : int write
val write_int8 : char write
val write_int8_as_string : char write
val write_int32 : int32 write
val write_int32_as_string : int32 write
val write_int64 : int64 write
val write_int64_as_string : int64 write

type 'a read = Yojson.lexer_state -> Lexing.lexbuf -> 'a

Expand Down
4 changes: 2 additions & 2 deletions atdgen/src/obuckle_emit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ let rec get_reader_name
match x with
Unit (_, Unit, Unit) -> decoder_ident "unit"
| Bool (_, Bool, Bool) -> decoder_ident "bool"
| Int (_, Int o, Int) ->
| Int (_, Int o, Int _) ->
decoder_ident (
match o with
| Int -> "int"
Expand Down Expand Up @@ -357,7 +357,7 @@ let rec get_writer_name
encoder_ident "unit"
| Bool (_, Bool, Bool) ->
encoder_ident "bool"
| Int (_, Int o, Int) ->
| Int (_, Int o, Int _) ->
encoder_ident (
match o with
| Int -> "int"
Expand Down
21 changes: 13 additions & 8 deletions atdgen/src/oj_emit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,18 @@ let rec get_writer_name
"Yojson.Safe.write_null"
| Bool (_, Bool, Bool) ->
"Yojson.Safe.write_bool"
| Int (_, Int o, Int) ->
(match o with
Int -> "Yojson.Safe.write_int"
| Char -> "Atdgen_runtime.Oj_run.write_int8"
| Int32 -> "Atdgen_runtime.Oj_run.write_int32"
| Int64 -> "Atdgen_runtime.Oj_run.write_int64"
| Float -> "Atdgen_runtime.Oj_run.write_float_as_int"
| Int (_, Int o, Int j) ->
(match o, j with
| Int, Int -> "Yojson.Safe.write_int"
| Int, String -> "Atdgen_runtime.Oj_run.write_int_as_string"
| Char, Int -> "Atdgen_runtime.Oj_run.write_int8"
| Char, String -> "Atdgen_runtime.Oj_run.write_int8_as_string"
| Int32, Int -> "Atdgen_runtime.Oj_run.write_int32"
| Int32, String -> "Atdgen_runtime.Oj_run.write_int32_as_string"
| Int64, Int -> "Atdgen_runtime.Oj_run.write_int64"
| Int64, String -> "Atdgen_runtime.Oj_run.write_int64_as_string"
| Float, Int -> "Atdgen_runtime.Oj_run.write_float_as_int"
| Float, String -> "Atdgen_runtime.Oj_run.write_float_as_int_string"
)

| Float (_, Float, Float j) ->
Expand Down Expand Up @@ -196,7 +201,7 @@ let rec get_reader_name
match x with
Unit (_, Unit, Unit) -> "Atdgen_runtime.Oj_run.read_null"
| Bool (_, Bool, Bool) -> "Atdgen_runtime.Oj_run.read_bool"
| Int (_, Int o, Int) ->
| Int (_, Int o, Int _) ->
(match o with
Int -> "Atdgen_runtime.Oj_run.read_int"
| Char -> "Atdgen_runtime.Oj_run.read_int8"
Expand Down
3 changes: 2 additions & 1 deletion atdgen/src/oj_mapping.ml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ let rec mapping_of_expr (x : type_expr) =
Bool (loc, Bool, Bool)
| "int" ->
let o = Ocaml.get_ocaml_int Json an in
Int (loc, Int o, Int)
let j = Json.get_json_int an in
Int (loc, Int o, Int j)
| "float" ->
let j = Json.get_json_float an in
Float (loc, Float, Float j)
Expand Down
24 changes: 18 additions & 6 deletions atdgen/test/dune
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,23 @@
(action (run %{bin:atdgen} -t %{deps})))

(rule
(targets test_int64_enc_t.ml test_int64_enc_t.mli)
(deps test_int64_enc.atd)
(targets test_int_t.ml test_int_t.mli)
(deps test_int.atd)
(action (run %{bin:atdgen} -t %{deps})))

(rule
(targets test_int64_enc_j.ml test_int64_enc_j.mli)
(deps test_int64_enc.atd)
(targets test_int_j.ml test_int_j.mli)
(deps test_int.atd)
(action (run %{bin:atdgen} -j %{deps})))

(rule
(targets test_int_with_string_repr_t.ml test_int_with_string_repr_t.mli)
(deps test_int_with_string_repr.atd)
(action (run %{bin:atdgen} -t %{deps})))

(rule
(targets test_int_with_string_repr_j.ml test_int_with_string_repr_j.mli)
(deps test_int_with_string_repr.atd)
(action (run %{bin:atdgen} -j %{deps})))

(rule
Expand Down Expand Up @@ -381,8 +391,10 @@
testv
test_unit_biniou_t
test_unit_biniou_b
test_int64_enc_t
test_int64_enc_j
test_int_t
test_int_j
test_int_with_string_repr_t
test_int_with_string_repr_j
test_atdgen_main
test_lib
test_ppx_t
Expand Down
51 changes: 43 additions & 8 deletions atdgen/test/test_atdgen_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,46 @@ let test_polymorphic_wrap () =
Test_polymorphic_wrap_j.t_of_string Yojson.Safe.read_string json_out in
check (x = x2)

let test_encoding_int64 () =
let encoded = Test_int64_enc_j.string_of_int64 Int64.max_int in
check (String.equal encoded {|"9223372036854775807"|})
let test_encoding_int () =
let encoded = Test_int_j.string_of_char 'A' in
check (String.equal encoded {|65|});
let encoded = Test_int_j.string_of_int32 Int32.max_int in
check (String.equal encoded {|2147483647|});
let encoded = Test_int_j.string_of_int64 Int64.max_int in
check (String.equal encoded {|9223372036854775807|});
let encoded = Test_int_j.string_of_afloat 123.456 in
check (String.equal encoded {|123|})

let test_encoding_decoding_int () =
let encoded = Test_int_j.string_of_char 'A' in
let decoded = Test_int_j.char_of_string encoded in
check (decoded = 'A');
let encoded = Test_int_j.string_of_int32 Int32.max_int in
let decoded = Test_int_j.int32_of_string encoded in
check (decoded = Int32.max_int);
let encoded = Test_int_j.string_of_int64 Int64.max_int in
let decoded = Test_int_j.int64_of_string encoded in
check (decoded = Int64.max_int)

let test_encoding_decoding_int64 () =
let encoded = Test_int64_enc_j.string_of_int64 Int64.max_int in
let decoded = Test_int64_enc_j.int64_of_string encoded in
let test_encoding_int_with_string_repr () =
let encoded = Test_int_with_string_repr_j.string_of_char 'A' in
check (String.equal encoded {|"65"|});
let encoded = Test_int_with_string_repr_j.string_of_int32 Int32.max_int in
check (String.equal encoded {|"2147483647"|});
let encoded = Test_int_with_string_repr_j.string_of_int64 Int64.max_int in
check (String.equal encoded {|"9223372036854775807"|});
let encoded = Test_int_with_string_repr_j.string_of_afloat 123.456 in
check (String.equal encoded {|"123"|})

let test_encoding_decoding_int_with_string_repr () =
let encoded = Test_int_with_string_repr_j.string_of_char 'A' in
let decoded = Test_int_with_string_repr_j.char_of_string encoded in
check (decoded = 'A');
let encoded = Test_int_with_string_repr_j.string_of_int32 Int32.max_int in
let decoded = Test_int_with_string_repr_j.int32_of_string encoded in
check (decoded = Int32.max_int);
let encoded = Test_int_with_string_repr_j.string_of_int64 Int64.max_int in
let decoded = Test_int_with_string_repr_j.int64_of_string encoded in
check (decoded = Int64.max_int)

let test_raw_json () =
Expand Down Expand Up @@ -683,8 +716,10 @@ let all_tests : (string * (unit -> unit)) list = [
"test ambiguous record with json adapters", test_ambiguous_record;
"test ambiguous classic variants with json adapters", test_ambiguous_classic_variants;
"test wrapping of polymorphic types", test_polymorphic_wrap;
"json encoding int64 as string", test_encoding_int64;
"json encoding & decoding int64", test_encoding_decoding_int64;
"json encoding int", test_encoding_int;
"json encoding & decoding int", test_encoding_decoding_int;
"json encoding int with string representation", test_encoding_int_with_string_repr;
"json encoding & decoding int with string representation", test_encoding_decoding_int_with_string_repr;
Comment on lines +721 to +722
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we have a couple of tests encoding ints as ints?

"abstract types", test_abstract_types;
"untyped json", test_untyped_json;
]
Expand Down
4 changes: 4 additions & 0 deletions atdgen/test/test_int.atd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type char = int <ocaml repr="char"> (* json repr should default to the same type as atd (int) *)
type int32 = int <ocaml repr="int32"> <json repr="int">
type int64 = int <ocaml repr="int64"> <json repr="int">
type afloat = int <ocaml repr="float"> <json repr="int">
1 change: 0 additions & 1 deletion atdgen/test/test_int64_enc.atd

This file was deleted.

4 changes: 4 additions & 0 deletions atdgen/test/test_int_with_string_repr.atd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type char = int <ocaml repr="char"> <json repr="string">
type int32 = int <ocaml repr="int32"> <json repr="string">
type int64 = int <ocaml repr="int64"> <json repr="string">
type afloat = int <ocaml repr="float"> <json repr="string">
16 changes: 16 additions & 0 deletions doc/atdgen-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,22 @@ Example:

type unixtime = float <json repr="int">

Ints
~~~~

Position: after ``int`` type

Values: ``string``

Semantics: specifies a int value that must be represented in JSON as
a string.

Example:

.. code:: ocaml

type int64 = int <ocaml repr="int64"> <json repr="string">

Field ``tag_field``
"""""""""""""""""""

Expand Down