···
1
-
(** Example: Validating JSON Feeds
1
+
(** Example: JSON Feed Validator
4
-
- Validating feed structure
5
-
- Testing various edge cases
6
-
- Handling invalid feeds
7
-
- Best practices for feed construction *)
3
+
Reads a JSON Feed from stdin and validates it.
6
+
feed_validator < feed.json
7
+
cat feed.json | feed_validator
11
-
let test_valid_minimal_feed () =
12
-
Format.printf "=== Test: Minimal Valid Feed ===\n";
11
+
1 - Feed parsing failed
12
+
2 - Feed validation failed *)
14
-
let feed = Jsonfeed.create
15
-
~title:"Minimal Feed"
14
+
let validate_stdin () =
15
+
let stdin = Bytesrw.Bytes.Reader.of_in_channel In_channel.stdin in
16
+
match Jsonfeed.decode ~locs:true stdin with
18
+
Format.eprintf "Parsing failed:\n %s\n%!" (Jsont.Error.to_string err);
21
+
match Jsonfeed.validate feed with
23
+
Format.printf "Feed is valid\n%!";
24
+
Format.printf "\nFeed details:\n";
25
+
Format.printf " Title: %s\n" (Jsonfeed.title feed);
26
+
Format.printf " Version: %s\n" (Jsonfeed.version feed);
27
+
(match Jsonfeed.home_page_url feed with
28
+
| Some url -> Format.printf " Home page: %s\n" url
30
+
Format.printf " Items: %d\n" (List.length (Jsonfeed.items feed));
19
-
match Jsonfeed.validate feed with
20
-
| Ok () -> Format.printf "✓ Minimal feed is valid\n\n"
22
-
Format.printf "✗ Minimal feed validation failed:\n";
23
-
List.iter (Format.printf " - %s\n") errors;
34
+
Format.eprintf "Validation failed:\n%!";
35
+
List.iter (fun err -> Format.eprintf " - %s\n%!" err) errors;
26
-
let test_valid_complete_feed () =
27
-
Format.printf "=== Test: Complete Valid Feed ===\n";
29
-
let author = Author.create
31
-
~url:"https://example.com/author"
32
-
~avatar:"https://example.com/avatar.png"
35
-
let attachment = Attachment.create
36
-
~url:"https://example.com/file.mp3"
37
-
~mime_type:"audio/mpeg"
39
-
~size_in_bytes:1024L
40
-
~duration_in_seconds:60
43
-
let item = Item.create
44
-
~id:"https://example.com/items/1"
45
-
~url:"https://example.com/items/1"
47
-
~content:(`Both ("<p>HTML content</p>", "Text content"))
48
-
~summary:"A test item"
49
-
~image:"https://example.com/image.jpg"
50
-
~banner_image:"https://example.com/banner.jpg"
51
-
~date_published:(Jsonfeed.Rfc3339.parse "2024-11-01T10:00:00Z" |> Option.get)
52
-
~date_modified:(Jsonfeed.Rfc3339.parse "2024-11-01T15:00:00Z" |> Option.get)
54
-
~tags:["test"; "example"]
56
-
~attachments:[attachment]
59
-
let hub = Hub.create
61
-
~url:"https://pubsubhubbub.appspot.com/"
64
-
let feed = Jsonfeed.create
65
-
~title:"Complete Feed"
66
-
~home_page_url:"https://example.com"
67
-
~feed_url:"https://example.com/feed.json"
68
-
~description:"A complete test feed"
69
-
~user_comment:"This is a test feed"
70
-
~icon:"https://example.com/icon.png"
71
-
~favicon:"https://example.com/favicon.ico"
78
-
match Jsonfeed.validate feed with
79
-
| Ok () -> Format.printf "✓ Complete feed is valid\n\n"
81
-
Format.printf "✗ Complete feed validation failed:\n";
82
-
List.iter (Format.printf " - %s\n") errors;
85
-
let test_feed_with_multiple_items () =
86
-
Format.printf "=== Test: Feed with Multiple Items ===\n";
88
-
let items = List.init 10 (fun i ->
90
-
~id:(Printf.sprintf "https://example.com/items/%d" i)
91
-
~content:(`Text (Printf.sprintf "Item %d content" i))
92
-
~title:(Printf.sprintf "Item %d" i)
93
-
~date_published:(Jsonfeed.Rfc3339.parse
94
-
(Printf.sprintf "2024-11-%02dT10:00:00Z" (i + 1)) |> Option.get)
98
-
let feed = Jsonfeed.create
99
-
~title:"Multi-item Feed"
103
-
match Jsonfeed.validate feed with
105
-
Format.printf "✓ Feed with %d items is valid\n\n" (List.length items)
107
-
Format.printf "✗ Multi-item feed validation failed:\n";
108
-
List.iter (Format.printf " - %s\n") errors;
111
-
let test_podcast_feed () =
112
-
Format.printf "=== Test: Podcast Feed ===\n";
114
-
let host = Author.create
115
-
~name:"Podcast Host"
116
-
~url:"https://podcast.example.com/host"
119
-
let episode1 = Attachment.create
120
-
~url:"https://podcast.example.com/ep1.mp3"
121
-
~mime_type:"audio/mpeg"
123
-
~size_in_bytes:20_971_520L (* 20 MB *)
124
-
~duration_in_seconds:1800 (* 30 minutes *)
127
-
(* Alternate format of the same episode *)
128
-
let episode1_aac = Attachment.create
129
-
~url:"https://podcast.example.com/ep1.aac"
130
-
~mime_type:"audio/aac"
132
-
~size_in_bytes:16_777_216L
133
-
~duration_in_seconds:1800
136
-
let item = Item.create
137
-
~id:"https://podcast.example.com/episodes/1"
138
-
~url:"https://podcast.example.com/episodes/1"
139
-
~title:"Episode 1: Introduction"
140
-
~content:(`Html "<p>Welcome to the first episode!</p>")
141
-
~date_published:(Jsonfeed.Rfc3339.parse "2024-11-01T12:00:00Z" |> Option.get)
143
-
~attachments:[episode1; episode1_aac]
144
-
~image:"https://podcast.example.com/ep1-cover.jpg"
147
-
let feed = Jsonfeed.create
148
-
~title:"Example Podcast"
149
-
~home_page_url:"https://podcast.example.com"
150
-
~feed_url:"https://podcast.example.com/feed.json"
155
-
match Jsonfeed.validate feed with
156
-
| Ok () -> Format.printf "✓ Podcast feed is valid\n\n"
158
-
Format.printf "✗ Podcast feed validation failed:\n";
159
-
List.iter (Format.printf " - %s\n") errors;
162
-
let test_microblog_feed () =
163
-
Format.printf "=== Test: Microblog Feed (no titles) ===\n";
165
-
let author = Author.create
166
-
~name:"Microblogger"
167
-
~url:"https://micro.example.com"
172
-
~id:"https://micro.example.com/1"
173
-
~content:(`Text "Just posted a new photo!")
174
-
~date_published:(Jsonfeed.Rfc3339.parse "2024-11-01T08:00:00Z" |> Option.get)
177
-
~id:"https://micro.example.com/2"
178
-
~content:(`Text "Having a great day! ☀️")
179
-
~date_published:(Jsonfeed.Rfc3339.parse "2024-11-01T12:30:00Z" |> Option.get)
182
-
~id:"https://micro.example.com/3"
183
-
~content:(`Html "<p>Check out this <a href=\"#\">link</a></p>")
184
-
~date_published:(Jsonfeed.Rfc3339.parse "2024-11-01T16:45:00Z" |> Option.get)
188
-
let feed = Jsonfeed.create
190
-
~home_page_url:"https://micro.example.com"
195
-
match Jsonfeed.validate feed with
197
-
Format.printf "✓ Microblog feed with %d items is valid\n\n"
198
-
(List.length items)
200
-
Format.printf "✗ Microblog feed validation failed:\n";
201
-
List.iter (Format.printf " - %s\n") errors;
204
-
let test_expired_feed () =
205
-
Format.printf "=== Test: Expired Feed ===\n";
207
-
let feed = Jsonfeed.create
208
-
~title:"Archived Blog"
209
-
~home_page_url:"https://archive.example.com"
210
-
~description:"This blog is no longer updated"
215
-
match Jsonfeed.validate feed with
216
-
| Ok () -> Format.printf "✓ Expired feed is valid\n\n"
218
-
Format.printf "✗ Expired feed validation failed:\n";
219
-
List.iter (Format.printf " - %s\n") errors;
222
-
let test_paginated_feed () =
223
-
Format.printf "=== Test: Paginated Feed ===\n";
225
-
let items = List.init 25 (fun i ->
227
-
~id:(Printf.sprintf "https://example.com/items/%d" i)
228
-
~content:(`Text (Printf.sprintf "Item %d" i))
232
-
let feed = Jsonfeed.create
233
-
~title:"Large Feed"
234
-
~home_page_url:"https://example.com"
235
-
~feed_url:"https://example.com/feed.json?page=1"
236
-
~next_url:"https://example.com/feed.json?page=2"
240
-
match Jsonfeed.validate feed with
242
-
Format.printf "✓ Paginated feed is valid (page 1 with next_url)\n\n"
244
-
Format.printf "✗ Paginated feed validation failed:\n";
245
-
List.iter (Format.printf " - %s\n") errors;
248
-
let test_invalid_feed_from_json () =
249
-
Format.printf "=== Test: Parsing Invalid JSON ===\n";
251
-
(* Missing required version field *)
252
-
let invalid_json1 = {|{
257
-
(match Jsonfeed.of_string invalid_json1 with
258
-
| Ok _ -> Format.printf "✗ Should have failed (missing version)\n"
260
-
Format.printf "✓ Correctly rejected invalid feed: %s\n" (Jsont.Error.to_string err));
262
-
(* Missing required title field *)
263
-
let invalid_json2 = {|{
264
-
"version": "https://jsonfeed.org/version/1.1",
268
-
(match Jsonfeed.of_string invalid_json2 with
269
-
| Ok _ -> Format.printf "✗ Should have failed (missing title)\n"
271
-
Format.printf "✓ Correctly rejected invalid feed: %s\n" (Jsont.Error.to_string err));
273
-
(* Item without id *)
274
-
let invalid_json3 = {|{
275
-
"version": "https://jsonfeed.org/version/1.1",
278
-
"content_text": "Hello"
282
-
(match Jsonfeed.of_string invalid_json3 with
283
-
| Ok _ -> Format.printf "✗ Should have failed (item without id)\n"
285
-
Format.printf "✓ Correctly rejected invalid feed: %s\n" (Jsont.Error.to_string err));
290
-
Format.printf "\n=== JSON Feed Validation Tests ===\n\n";
292
-
test_valid_minimal_feed ();
293
-
test_valid_complete_feed ();
294
-
test_feed_with_multiple_items ();
295
-
test_podcast_feed ();
296
-
test_microblog_feed ();
297
-
test_expired_feed ();
298
-
test_paginated_feed ();
299
-
test_invalid_feed_from_json ();
301
-
Format.printf "=== All Tests Complete ===\n"
38
+
let () = validate_stdin ()