OCaml library for JSONfeed parsing and creation

tests for the roundtripping

+7 -2
CHANGES.md
···
-
v1.0.0
-
------
+
v1.1.0 (dev)
+
------------
+
+
- Simplify round trip processing of unknown messages using Jsont combinators (@avsm).
+
+
v1.0.0 (2025-11-12)
+
-------------------
- Initial public release (@avsm)
+1 -1
dune-project
···
(ptime (>= 1.2.0))
bytesrw
(odoc :with-doc)
-
(alcotest (and :with-test (>= 1.9.0)))))
+
(alcotest (and :with-test (>= 1.8.0)))))
+1 -1
jsonfeed.opam
···
"ptime" {>= "1.2.0"}
"bytesrw"
"odoc" {with-doc}
-
"alcotest" {with-test & >= "1.9.0"}
+
"alcotest" {with-test & >= "1.8.0"}
]
build: [
["dune" "subst"] {dev}
+180
test/test_jsonfeed.ml
···
test_feed_parse_invalid_missing_content );
]
+
(* Unknown fields preservation tests *)
+
+
let test_author_unknown_roundtrip () =
+
let json =
+
{|{
+
"name": "Test Author",
+
"custom_field": "custom value",
+
"another_extension": 42
+
}|}
+
in
+
match Jsont_bytesrw.decode_string' Author.jsont json with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Parse failed: %s" (Jsont.Error.to_string e))
+
| Ok author ->
+
(* Check that unknown fields are preserved *)
+
let unknown = Author.unknown author in
+
Alcotest.(check bool)
+
"has unknown fields" false
+
(Jsonfeed.Unknown.is_empty unknown);
+
(* Encode and decode again *)
+
(match Jsont_bytesrw.encode_string' Author.jsont author with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Encode failed: %s" (Jsont.Error.to_string e))
+
| Ok json2 -> (
+
match Jsont_bytesrw.decode_string' Author.jsont json2 with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Re-parse failed: %s" (Jsont.Error.to_string e))
+
| Ok author2 ->
+
(* Verify unknown fields survive roundtrip *)
+
let unknown2 = Author.unknown author2 in
+
Alcotest.(check bool)
+
"unknown fields preserved" false
+
(Jsonfeed.Unknown.is_empty unknown2)))
+
+
let test_item_unknown_roundtrip () =
+
let json =
+
{|{
+
"id": "https://example.com/1",
+
"content_html": "<p>Test</p>",
+
"custom_metadata": "some custom data",
+
"x_custom_number": 123.45
+
}|}
+
in
+
match Jsont_bytesrw.decode_string' Item.jsont json with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Parse failed: %s" (Jsont.Error.to_string e))
+
| Ok item ->
+
(* Check that unknown fields are preserved *)
+
let unknown = Item.unknown item in
+
Alcotest.(check bool)
+
"has unknown fields" false
+
(Jsonfeed.Unknown.is_empty unknown);
+
(* Encode and decode again *)
+
(match Jsont_bytesrw.encode_string' Item.jsont item with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Encode failed: %s" (Jsont.Error.to_string e))
+
| Ok json2 -> (
+
match Jsont_bytesrw.decode_string' Item.jsont json2 with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Re-parse failed: %s" (Jsont.Error.to_string e))
+
| Ok item2 ->
+
let unknown2 = Item.unknown item2 in
+
Alcotest.(check bool)
+
"unknown fields preserved" false
+
(Jsonfeed.Unknown.is_empty unknown2)))
+
+
let test_feed_unknown_roundtrip () =
+
let json =
+
{|{
+
"version": "https://jsonfeed.org/version/1.1",
+
"title": "Test Feed",
+
"items": [],
+
"custom_extension": "custom value",
+
"x_another_field": {"nested": "data"}
+
}|}
+
in
+
match Jsonfeed.of_string json with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Parse failed: %s" (Jsont.Error.to_string e))
+
| Ok feed ->
+
(* Check that unknown fields are preserved *)
+
let unknown = Jsonfeed.unknown feed in
+
Alcotest.(check bool)
+
"has unknown fields" false
+
(Jsonfeed.Unknown.is_empty unknown);
+
(* Encode and decode again *)
+
(match Jsonfeed.to_string feed with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Encode failed: %s" (Jsont.Error.to_string e))
+
| Ok json2 -> (
+
match Jsonfeed.of_string json2 with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Re-parse failed: %s" (Jsont.Error.to_string e))
+
| Ok feed2 ->
+
let unknown2 = Jsonfeed.unknown feed2 in
+
Alcotest.(check bool)
+
"unknown fields preserved" false
+
(Jsonfeed.Unknown.is_empty unknown2)))
+
+
let test_hub_unknown_roundtrip () =
+
let json = {|{
+
"type": "WebSub",
+
"url": "https://example.com/hub",
+
"custom_field": "test"
+
}|} in
+
match Jsont_bytesrw.decode_string' Hub.jsont json with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Parse failed: %s" (Jsont.Error.to_string e))
+
| Ok hub ->
+
let unknown = Hub.unknown hub in
+
Alcotest.(check bool)
+
"has unknown fields" false
+
(Jsonfeed.Unknown.is_empty unknown);
+
(match Jsont_bytesrw.encode_string' Hub.jsont hub with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Encode failed: %s" (Jsont.Error.to_string e))
+
| Ok json2 -> (
+
match Jsont_bytesrw.decode_string' Hub.jsont json2 with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Re-parse failed: %s" (Jsont.Error.to_string e))
+
| Ok hub2 ->
+
let unknown2 = Hub.unknown hub2 in
+
Alcotest.(check bool)
+
"unknown fields preserved" false
+
(Jsonfeed.Unknown.is_empty unknown2)))
+
+
let test_attachment_unknown_roundtrip () =
+
let json =
+
{|{
+
"url": "https://example.com/file.mp3",
+
"mime_type": "audio/mpeg",
+
"x_custom": "value"
+
}|}
+
in
+
match Jsont_bytesrw.decode_string' Attachment.jsont json with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Parse failed: %s" (Jsont.Error.to_string e))
+
| Ok att ->
+
let unknown = Attachment.unknown att in
+
Alcotest.(check bool)
+
"has unknown fields" false
+
(Jsonfeed.Unknown.is_empty unknown);
+
(match Jsont_bytesrw.encode_string' Attachment.jsont att with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Encode failed: %s" (Jsont.Error.to_string e))
+
| Ok json2 -> (
+
match Jsont_bytesrw.decode_string' Attachment.jsont json2 with
+
| Error e ->
+
Alcotest.fail
+
(Printf.sprintf "Re-parse failed: %s" (Jsont.Error.to_string e))
+
| Ok att2 ->
+
let unknown2 = Attachment.unknown att2 in
+
Alcotest.(check bool)
+
"unknown fields preserved" false
+
(Jsonfeed.Unknown.is_empty unknown2)))
+
+
let unknown_fields_tests =
+
[
+
("author unknown roundtrip", `Quick, test_author_unknown_roundtrip);
+
("item unknown roundtrip", `Quick, test_item_unknown_roundtrip);
+
("feed unknown roundtrip", `Quick, test_feed_unknown_roundtrip);
+
("hub unknown roundtrip", `Quick, test_hub_unknown_roundtrip);
+
("attachment unknown roundtrip", `Quick, test_attachment_unknown_roundtrip);
+
]
+
(* Main test suite *)
let () =
···
("Hub", hub_tests);
("Item", item_tests);
("Jsonfeed", jsonfeed_tests);
+
("Unknown Fields", unknown_fields_tests);
]