OCaml library for JSONfeed parsing and creation
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** JSON Feed format parser and serializer.
7
8 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
9
10type t
11(** The type representing a complete JSON Feed. *)
12
13val jsont : t Jsont.t
14(** Declarative type that describes the structure of JSON Feeds.
15
16 Maps the complete JSON Feed 1.1 specification including all required and
17 optional fields. *)
18
19module Unknown : sig
20 type t = (string * Jsont.json) list
21 (** Unknown or unrecognized JSON object members. Useful for preserving fields
22 from custom extensions or future spec versions. *)
23
24 val empty : t
25 (** [empty] is the empty list of unknown fields. *)
26
27 val is_empty : t -> bool
28 (** [is_empty u] returns [true] if there are no unknown fields. *)
29end
30
31(** {1 Construction} *)
32
33val create :
34 title:string ->
35 ?home_page_url:string ->
36 ?feed_url:string ->
37 ?description:string ->
38 ?user_comment:string ->
39 ?next_url:string ->
40 ?icon:string ->
41 ?favicon:string ->
42 ?authors:Author.t list ->
43 ?language:string ->
44 ?expired:bool ->
45 ?hubs:Hub.t list ->
46 items:Item.t list ->
47 ?unknown:Unknown.t ->
48 unit ->
49 t
50(** [create ~title ~items ()] creates a new JSON Feed.
51
52 @param title
53 The name of the feed. Required field that should be plain text, not HTML.
54 @param home_page_url
55 The URL of the resource that the feed describes. This resource may or may
56 not actually be a "home" page, but it should be an HTML page. If a feed is
57 for a podcast, for instance, the home_page_url would be the URL for the
58 podcast's website.
59 @param feed_url
60 The URL of the feed itself. This is the URL that was requested to get this
61 JSON Feed response. Helps feed readers to determine when they're being
62 redirected. Strongly recommended for feeds.
63 @param description
64 A plain text description of the feed, for human consumption. May contain
65 some formatting (like newlines).
66 @param user_comment
67 A description of the purpose of the feed, for a person looking at the raw
68 JSON. This is for the publisher's use only, not intended to be displayed
69 to the user.
70 @param next_url
71 The URL of a feed that provides the next n items, where n is determined by
72 the publisher. Used for pagination. A feed reader may continue to request
73 the URLs in next_url until it reaches a feed without a next_url.
74 @param icon
75 The URL of an image for the feed suitable to be used in a timeline, much
76 the way an avatar might be used. Should be square and relatively large -
77 such as 512 x 512 pixels - and may be cropped to a circle or rounded
78 corners. Should not be transparent.
79 @param favicon
80 The URL of an image for the feed suitable to be used in a source list.
81 Should be square and relatively small - such as 64 x 64 pixels. Should not
82 be transparent.
83 @param authors
84 Specifies one or more feed authors. The author object has several members
85 (name, url, avatar) which are all optional, but at least one must be
86 present for the object to be valid.
87 @param language
88 The primary language for the feed in RFC 5646 format. The value can be a
89 language tag such as "en" or "en-US", or a language-region combination.
90 @param expired
91 Whether or not the feed is finished - that is, whether or not it will ever
92 update again. A feed for a temporary event, like an instance of a
93 conference, may expire. If the value is [true], feed readers may stop
94 checking for updates.
95 @param hubs
96 Endpoints that can be used to subscribe to real-time notifications from
97 the publisher of this feed. Each hub object has a type (such as "rssCloud"
98 or "WebSub") and url.
99 @param items
100 The items in the feed. Required field, though it may be an empty array.
101 @param unknown
102 Unknown JSON object members preserved from parsing. Useful for custom
103 extensions. *)
104
105(** {1 Accessors} *)
106
107val version : t -> string
108(** [version feed] returns the URL of the version of the format the feed uses.
109 This will always be "https://jsonfeed.org/version/1.1" for feeds created
110 with this library. This is a required field in the JSON Feed spec. *)
111
112val title : t -> string
113(** [title feed] returns the name of the feed. This is plain text and should not
114 contain HTML. This is a required field. *)
115
116val home_page_url : t -> string option
117(** [home_page_url feed] returns the URL of the resource that the feed
118 describes. This resource may or may not actually be a "home" page, but it
119 should be an HTML page. For instance, if a feed is for a podcast, the
120 home_page_url would be the URL for the podcast's website. *)
121
122val feed_url : t -> string option
123(** [feed_url feed] returns the URL of the feed itself. This should be the URL
124 that was requested to get this JSON Feed response. It helps feed readers
125 determine when they're being redirected. This is strongly recommended for
126 feeds. *)
127
128val description : t -> string option
129(** [description feed] returns a plain text description of the feed, for human
130 consumption. This field may contain some formatting such as newlines. *)
131
132val user_comment : t -> string option
133(** [user_comment feed] returns a description of the purpose of the feed, for a
134 person looking at the raw JSON. This is for the publisher's use only and is
135 not intended to be displayed to end users. *)
136
137val next_url : t -> string option
138(** [next_url feed] returns the URL of a feed that provides the next n items,
139 where n is determined by the publisher. This is used for pagination. A feed
140 reader may continue to request the URLs in next_url until it reaches a feed
141 without a next_url. *)
142
143val icon : t -> string option
144(** [icon feed] returns the URL of an image for the feed suitable to be used in
145 a timeline, much the way an avatar might be used. It should be square and
146 relatively large (such as 512 x 512 pixels) and may be cropped to a circle
147 or rounded corners by feed readers. It should not be transparent. *)
148
149val favicon : t -> string option
150(** [favicon feed] returns the URL of an image for the feed suitable to be used
151 in a source list. It should be square and relatively small (such as 64 x 64
152 pixels) and should not be transparent. *)
153
154val authors : t -> Author.t list option
155(** [authors feed] returns the feed authors. Each author object has several
156 members (name, url, avatar) which are all optional, but at least one must be
157 present for the object to be valid. If a feed has multiple authors, they
158 should all be listed here. *)
159
160val language : t -> string option
161(** [language feed] returns the primary language for the feed in RFC 5646
162 format. The value can be a language tag such as "en" or "en-US", or a
163 language-region combination. This field helps feed readers present the feed
164 in the appropriate language. *)
165
166val expired : t -> bool option
167(** [expired feed] returns whether the feed is finished - that is, whether it
168 will ever update again. A feed for a temporary event, like an instance of a
169 conference, may expire. If the value is [Some true], feed readers may stop
170 checking for updates. *)
171
172val hubs : t -> Hub.t list option
173(** [hubs feed] returns endpoints that can be used to subscribe to real-time
174 notifications from the publisher of this feed. Each hub object has a type
175 (such as "rssCloud" or "WebSub") and a url. Feed readers can use these to
176 get immediate updates when new items are published. *)
177
178val items : t -> Item.t list
179(** [items feed] returns the array of items in the feed. This is a required
180 field, though it may be an empty list. Items represent the individual
181 entries in the feed - blog posts, podcast episodes, microblog posts, etc. *)
182
183val unknown : t -> Unknown.t
184(** [unknown feed] returns any unknown JSON object members that were preserved
185 during parsing. This is useful for custom extensions or fields from future
186 versions of the spec. *)
187
188(** {1 Encoding and Decoding} *)
189
190val decode :
191 ?layout:bool ->
192 ?locs:bool ->
193 ?file:string ->
194 Bytesrw.Bytes.Reader.t ->
195 (t, Jsont.Error.t) result
196(** [decode r] decodes a JSON Feed from bytesrw reader [r].
197
198 @param layout Preserve whitespace for round-tripping (default: false)
199 @param locs Track locations for better error messages (default: false)
200 @param file Source file name for error reporting *)
201
202val decode_string :
203 ?layout:bool ->
204 ?locs:bool ->
205 ?file:string ->
206 string ->
207 (t, Jsont.Error.t) result
208(** [decode_string s] decodes a JSON Feed from string [s]. *)
209
210val encode :
211 ?format:Jsont.format ->
212 ?number_format:Jsont.number_format ->
213 t ->
214 eod:bool ->
215 Bytesrw.Bytes.Writer.t ->
216 (unit, Jsont.Error.t) result
217(** [encode feed w] encodes [feed] to bytesrw writer [w].
218
219 @param format
220 Output formatting: [Jsont.Minify] or [Jsont.Indent] (default: Minify)
221 @param number_format Printf format for numbers (default: "%.16g")
222 @param eod Write end-of-data marker *)
223
224val encode_string :
225 ?format:Jsont.format ->
226 ?number_format:Jsont.number_format ->
227 t ->
228 (string, Jsont.Error.t) result
229(** [encode_string feed] encodes [feed] to a string. *)
230
231val of_string : string -> (t, Jsont.Error.t) result
232(** Alias for [decode_string] with default options. *)
233
234val to_string : ?minify:bool -> t -> (string, Jsont.Error.t) result
235(** [to_string feed] encodes [feed] to string.
236 @param minify Use compact format (true) or indented (false, default) *)
237
238(** {1 Validation} *)
239
240val validate : t -> (unit, string list) result
241(** [validate feed] validates the feed structure. Checks for unique item IDs,
242 valid content, etc. *)
243
244(** {1 Comparison} *)
245
246val equal : t -> t -> bool
247(** [equal a b] tests equality between two feeds. *)
248
249(** {1 Pretty Printing} *)
250
251val pp : Format.formatter -> t -> unit
252val pp_summary : Format.formatter -> t -> unit
253
254(** {1 Submodules} *)
255
256module Rfc3339 = Rfc3339
257module Cito = Cito
258module Author = Author
259module Attachment = Attachment
260module Hub = Hub
261module Reference = Reference
262module Item = Item