OCaml library for JSONfeed parsing and creation
1(** JSON Feed format parser and serializer.
2
3 This library implements the JSON Feed specification version 1.1, providing
4 type-safe parsing and serialization of JSON Feed documents. JSON Feed is a
5 syndication format similar to RSS and Atom, but using JSON instead of XML.
6
7 {b Quick Start:}
8 {[
9 (* Create a simple feed *)
10 let feed = Jsonfeed.create
11 ~title:"My Blog"
12 ~home_page_url:"https://example.com"
13 ~feed_url:"https://example.com/feed.json"
14 ~items:[
15 Item.create
16 ~id:"https://example.com/post/1"
17 ~content:(Item.Html "<p>Hello, world!</p>")
18 ~title:"First Post"
19 ()
20 ]
21 ()
22
23 (* Serialize to string *)
24 let json = Jsonfeed.to_string feed
25
26 (* Parse from string *)
27 match Jsonfeed.of_string json with
28 | Ok feed -> Printf.printf "Feed: %s\n" (Jsonfeed.title feed)
29 | Error err -> Printf.eprintf "Error: %s\n" err
30 ]}
31
32 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
33
34
35(** The type representing a complete JSON Feed. *)
36type t
37
38(** Exception raised when attempting to parse an invalid feed. *)
39exception Invalid_feed of string
40
41(** {1 Construction} *)
42
43(** [create ~title ?home_page_url ?feed_url ?description ?user_comment ?next_url
44 ?icon ?favicon ?authors ?language ?expired ?hubs ~items ()]
45 creates a JSON Feed.
46
47 @param title The name of the feed (required)
48 @param home_page_url The URL of the resource the feed describes
49 @param feed_url The URL of the feed itself (serves as unique identifier)
50 @param description Additional information about the feed
51 @param user_comment A description of the feed's purpose for humans reading the raw JSON
52 @param next_url URL of the next page of items (for pagination)
53 @param icon The feed's icon URL (should be square, 512x512 or larger)
54 @param favicon The feed's favicon URL (should be square, 64x64 or larger)
55 @param authors The feed's default authors (inherited by items without authors)
56 @param language The primary language of the feed (RFC 5646 format, e.g. ["en-US"])
57 @param expired Whether the feed will update again ([true] means no more updates)
58 @param hubs Endpoints for real-time notifications
59 @param items The list of feed items (required)
60
61 {b Examples:}
62 {[
63 (* Minimal feed *)
64 let feed = Jsonfeed.create
65 ~title:"My Blog"
66 ~items:[] ()
67
68 (* Full-featured blog feed *)
69 let feed = Jsonfeed.create
70 ~title:"Example Blog"
71 ~home_page_url:"https://example.com"
72 ~feed_url:"https://example.com/feed.json"
73 ~description:"A blog about OCaml and functional programming"
74 ~icon:"https://example.com/icon.png"
75 ~authors:[
76 Author.create
77 ~name:"Jane Doe"
78 ~url:"https://example.com/about"
79 ()
80 ]
81 ~language:"en-US"
82 ~items:[
83 Item.create
84 ~id:"https://example.com/posts/1"
85 ~content:(Item.Html "<p>First post</p>")
86 ~title:"Hello World"
87 ();
88 Item.create
89 ~id:"https://example.com/posts/2"
90 ~content:(Item.Html "<p>Second post</p>")
91 ~title:"Another Post"
92 ()
93 ]
94 ()
95
96 (* Podcast feed with hubs *)
97 let hub = Hub.create
98 ~type_:"WebSub"
99 ~url:"https://pubsubhubbub.appspot.com/"
100 () in
101 let feed = Jsonfeed.create
102 ~title:"My Podcast"
103 ~home_page_url:"https://podcast.example.com"
104 ~feed_url:"https://podcast.example.com/feed.json"
105 ~hubs:[hub]
106 ~items:[
107 Item.create
108 ~id:"https://podcast.example.com/episodes/1"
109 ~content:(Item.Html "<p>Episode description</p>")
110 ~title:"Episode 1"
111 ~attachments:[
112 Attachment.create
113 ~url:"https://podcast.example.com/ep1.mp3"
114 ~mime_type:"audio/mpeg"
115 ~duration_in_seconds:1800
116 ()
117 ]
118 ()
119 ]
120 ()
121 ]} *)
122val create :
123 title:string ->
124 ?home_page_url:string ->
125 ?feed_url:string ->
126 ?description:string ->
127 ?user_comment:string ->
128 ?next_url:string ->
129 ?icon:string ->
130 ?favicon:string ->
131 ?authors:Author.t list ->
132 ?language:string ->
133 ?expired:bool ->
134 ?hubs:Hub.t list ->
135 items:Item.t list ->
136 unit ->
137 t
138
139
140(** {1 Accessors} *)
141
142(** [version t] returns the JSON Feed version URL.
143
144 This is always ["https://jsonfeed.org/version/1.1"] for feeds created
145 by this library, but may differ when parsing external feeds. *)
146val version : t -> string
147
148(** [title t] returns the feed's title. *)
149val title : t -> string
150
151(** [home_page_url t] returns the feed's home page URL, if set. *)
152val home_page_url : t -> string option
153
154(** [feed_url t] returns the feed's URL, if set. *)
155val feed_url : t -> string option
156
157(** [description t] returns the feed's description, if set. *)
158val description : t -> string option
159
160(** [user_comment t] returns the feed's user comment, if set. *)
161val user_comment : t -> string option
162
163(** [next_url t] returns the URL for the next page of items, if set. *)
164val next_url : t -> string option
165
166(** [icon t] returns the feed's icon URL, if set. *)
167val icon : t -> string option
168
169(** [favicon t] returns the feed's favicon URL, if set. *)
170val favicon : t -> string option
171
172(** [authors t] returns the feed's default authors, if set. *)
173val authors : t -> Author.t list option
174
175(** [language t] returns the feed's primary language, if set. *)
176val language : t -> string option
177
178(** [expired t] returns whether the feed will update again. *)
179val expired : t -> bool option
180
181(** [hubs t] returns the feed's hub endpoints, if set. *)
182val hubs : t -> Hub.t list option
183
184(** [items t] returns the feed's items. *)
185val items : t -> Item.t list
186
187
188(** {1 Parsing and Serialization} *)
189
190(** Error type for parsing operations. *)
191type error = string
192
193(** [of_jsonm decoder] parses a JSON Feed from a Jsonm decoder.
194
195 This is the lowest-level parsing function, suitable for integration
196 with streaming JSON processing pipelines.
197
198 @param decoder A Jsonm decoder positioned at the start of a JSON Feed document
199 @return [Ok feed] on success, [Error err] on parse error
200
201 {b Example:}
202 {[
203 let decoder = Jsonm.decoder (`String json_string) in
204 match Jsonfeed.of_jsonm decoder with
205 | Ok feed -> (* process feed *)
206 | Error err -> (* handle error *)
207 ]} *)
208val of_jsonm : Jsonm.decoder -> (t, error) result
209
210(** [to_jsonm encoder feed] serializes a JSON Feed to a Jsonm encoder.
211
212 This is the lowest-level serialization function, suitable for integration
213 with streaming JSON generation pipelines.
214
215 @param encoder A Jsonm encoder
216 @param feed The feed to serialize
217
218 {b Example:}
219 {[
220 let buffer = Buffer.create 1024 in
221 let encoder = Jsonm.encoder (`Buffer buffer) in
222 Jsonfeed.to_jsonm encoder feed;
223 let json = Buffer.contents buffer
224 ]} *)
225val to_jsonm : Jsonm.encoder -> t -> unit
226
227(** [of_string s] parses a JSON Feed from a string.
228
229 @param s A JSON string containing a JSON Feed document
230 @return [Ok feed] on success, [Error err] on parse error
231
232 {b Example:}
233 {[
234 let json = {|{
235 "version": "https://jsonfeed.org/version/1.1",
236 "title": "My Feed",
237 "items": []
238 }|} in
239 match Jsonfeed.of_string json with
240 | Ok feed -> Printf.printf "Parsed: %s\n" (Jsonfeed.title feed)
241 | Error err -> Printf.eprintf "Error: %s\n" err
242 ]} *)
243val of_string : string -> (t, error) result
244
245(** [to_string ?minify feed] serializes a JSON Feed to a string.
246
247 @param minify If [true], produces compact JSON without whitespace.
248 If [false] (default), produces indented, human-readable JSON.
249 @param feed The feed to serialize
250 @return A JSON string
251
252 {b Example:}
253 {[
254 let json = Jsonfeed.to_string feed
255 let compact = Jsonfeed.to_string ~minify:true feed
256 ]} *)
257val to_string : ?minify:bool -> t -> string
258
259
260(** {1 Date Utilities} *)
261
262(** [parse_rfc3339 s] parses an RFC 3339 date/time string.
263
264 This function parses timestamps in the format required by JSON Feed,
265 such as ["2024-11-03T10:30:00Z"] or ["2024-11-03T10:30:00-08:00"].
266
267 @param s An RFC 3339 formatted date/time string
268 @return [Some time] on success, [None] if the string is invalid
269
270 {b Examples:}
271 {[
272 parse_rfc3339 "2024-11-03T10:30:00Z"
273 (* returns Some time *)
274
275 parse_rfc3339 "2024-11-03T10:30:00-08:00"
276 (* returns Some time *)
277
278 parse_rfc3339 "invalid"
279 (* returns None *)
280 ]} *)
281val parse_rfc3339 : string -> Ptime.t option
282
283(** [format_rfc3339 time] formats a timestamp as an RFC 3339 string.
284
285 The output uses UTC timezone (Z suffix) and includes fractional seconds
286 if the timestamp has sub-second precision.
287
288 @param time A Ptime timestamp
289 @return An RFC 3339 formatted string
290
291 {b Example:}
292 {[
293 let now = Ptime_clock.now () in
294 let s = format_rfc3339 now
295 (* returns "2024-11-03T10:30:45.123Z" or similar *)
296 ]} *)
297val format_rfc3339 : Ptime.t -> string
298
299
300(** {1 Validation} *)
301
302(** [validate feed] validates a JSON Feed.
303
304 Checks that:
305 - All required fields are present
306 - All items have unique IDs
307 - All items have valid content
308 - All URLs are well-formed (if possible)
309 - Authors have at least one field set
310
311 @param feed The feed to validate
312 @return [Ok ()] if valid, [Error errors] with a list of validation issues
313
314 {b Example:}
315 {[
316 match Jsonfeed.validate feed with
317 | Ok () -> (* feed is valid *)
318 | Error errors ->
319 List.iter (Printf.eprintf "Validation error: %s\n") errors
320 ]} *)
321val validate : t -> (unit, string list) result
322
323
324(** {1 Comparison} *)
325
326(** [equal a b] tests equality between two feeds.
327
328 Feeds are compared structurally, including all fields and items. *)
329val equal : t -> t -> bool
330
331
332(** {1 Pretty Printing} *)
333
334(** [pp ppf feed] pretty prints a feed to the formatter.
335
336 The output is human-readable and suitable for debugging. It shows
337 the feed's metadata and a summary of items.
338
339 {b Example output:}
340 {v
341 Feed: My Blog (https://example.com)
342 Items: 2
343 Authors: Jane Doe
344 Language: en-US
345 v} *)
346val pp : Format.formatter -> t -> unit
347
348(** [pp_summary ppf feed] prints a brief summary of the feed.
349
350 Shows only the title and item count.
351
352 {b Example output:}
353 {v My Blog (2 items) v} *)
354val pp_summary : Format.formatter -> t -> unit
355
356
357(** {1 Feed Content} *)
358
359(** Author information for feeds and items. *)
360module Author = Author
361
362(** Attachments for feed items (audio, video, downloads). *)
363module Attachment = Attachment
364
365(** Hub endpoints for real-time notifications. *)
366module Hub = Hub
367
368(** Feed items (posts, episodes, entries). *)
369module Item = Item
370
371(** References to cited sources in items (extension). *)
372module Reference = Reference
373
374(** Citation Typing Ontology annotations for references (extension). *)
375module Cito = Cito