1#![doc = include_str!("../README.md")]
10#![cfg_attr(not(feature = "std"), no_std)]
11#![cfg_attr(docsrs, feature(doc_cfg))]
12#![warn(
13 missing_copy_implementations,
14 missing_debug_implementations,
15 missing_docs,
16 rust_2018_idioms,
17 unused_lifetimes,
18 unused_qualifications
19)]
20
21#[cfg(all(feature = "alloc", not(feature = "std")))]
22extern crate alloc;
23
24use core::str;
25
26#[cfg(all(feature = "alloc", not(feature = "std")))]
27use alloc::{
28 collections::BTreeSet,
29 string::{String, ToString},
30 vec::Vec,
31};
32#[cfg(feature = "std")]
33use std::{
34 collections::BTreeSet,
35 string::{String, ToString},
36 vec::Vec,
37};
38
39use serde_json::{Map, Value};
40
41pub const VERSION_1: &str = "https://jsonfeed.org/version/1";
43
44pub const VERSION_1_1: &str = "https://jsonfeed.org/version/1.1";
46
47#[derive(Clone, Debug, Eq, PartialEq)]
49pub enum Version<'a> {
50 Version1,
52 Version1_1,
54 Unknown(&'a str),
56}
57
58impl<'a> AsRef<str> for Version<'a> {
59 fn as_ref(&self) -> &str {
60 match self {
61 Version::Version1 => VERSION_1,
62 Version::Version1_1 => VERSION_1_1,
63 Version::Unknown(v) => v,
64 }
65 }
66}
67
68impl<'a> From<&'a str> for Version<'a> {
69 fn from(value: &'a str) -> Self {
70 match value {
71 VERSION_1 => Version::Version1,
72 VERSION_1_1 => Version::Version1_1,
73 _ => Version::Unknown(value),
74 }
75 }
76}
77
78impl<'a> core::fmt::Display for Version<'a> {
79 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80 write!(f, "{}", self.as_ref())
81 }
82}
83
84#[derive(Debug)]
86#[non_exhaustive]
87pub enum Error {
88 UnexpectedType,
93 SerdeJson(serde_json::Error),
95}
96
97impl From<serde_json::Error> for Error {
98 fn from(error: serde_json::Error) -> Self {
99 Error::SerdeJson(error)
100 }
101}
102
103macro_rules! get_set_rm_str {
104 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
105 get_set_rm_str!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
106
107 #[doc=$remover_doc]
108 pub fn $remover(&mut self) -> Option<Value> {
109 self.value.remove($key_expr)
110 }
111 };
112
113 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
114 get_set_rm_str!($key_expr, $getter, $getter_doc);
115
116 #[doc=$setter_doc]
117 pub fn $setter<T>(&mut self, value: T) -> Option<Value>
118 where
119 T: ToString,
120 {
121 self.value
122 .insert(String::from($key_expr), Value::String(value.to_string()))
123 }
124 };
125
126 ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
127 #[doc=$getter_doc]
128 pub fn $getter(&self) -> Result<Option<&str>, Error> {
129 self.value.get($key_expr).map_or_else(
130 || Ok(None),
131 |value| match value {
132 Value::String(s) => Ok(Some(s.as_str())),
133 _ => Err(Error::UnexpectedType),
134 },
135 )
136 }
137 };
138}
139
140macro_rules! get_set_rm_str_array {
141 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
142 get_set_rm_str_array!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
143
144 #[doc=$remover_doc]
145 pub fn $remover(&mut self) -> Option<Value> {
146 self.value.remove($key_expr)
147 }
148 };
149
150 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
151 get_set_rm_str_array!($key_expr, $getter, $getter_doc);
152
153 #[doc=$setter_doc]
154 pub fn $setter<I>(&mut self, values: I) -> Option<Value>
155 where
156 I: IntoIterator<Item = String>,
157 {
158 let values: Value = Value::Array(values.into_iter().map(Value::String).collect());
159 self.value.insert(String::from($key_expr), values)
160 }
161 };
162
163 ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
164 #[doc=$getter_doc]
165 pub fn $getter(&self) -> Result<Option<Vec<&str>>, Error> {
166 self.value.get($key_expr).map_or_else(
167 || Ok(None),
168 |value| match value {
169 Value::Array(arr) => arr
170 .iter()
171 .map(|value| match value {
172 Value::String(s) => Ok(s.as_str()),
173 _ => Err(Error::UnexpectedType),
174 })
175 .collect::<Result<Vec<&str>, Error>>()
176 .map(Some),
177 _ => Err(Error::UnexpectedType),
178 },
179 )
180 }
181 };
182}
183
184macro_rules! get_set_rm_bool {
185 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
186 get_set_rm_bool!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
187
188 #[doc=$remover_doc]
189 pub fn $remover(&mut self) -> Option<Value> {
190 self.value.remove($key_expr)
191 }
192 };
193
194 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
195 get_set_rm_bool!($key_expr, $getter, $getter_doc);
196
197 #[doc=$setter_doc]
198 pub fn $setter<T>(&mut self, value: bool) -> Option<Value> {
199 self.value
200 .insert(String::from($key_expr), Value::Bool(value))
201 }
202 };
203
204 ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
205 #[doc=$getter_doc]
206 pub fn $getter(&self) -> Result<Option<bool>, Error> {
207 self.value.get($key_expr).map_or_else(
208 || Ok(None),
209 |value| match value {
210 Value::Bool(b) => Ok(Some(*b)),
211 _ => Err(Error::UnexpectedType),
212 },
213 )
214 }
215 };
216}
217
218macro_rules! get_set_rm_u64 {
219 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
220 get_set_rm_u64!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
221
222 #[doc=$remover_doc]
223 pub fn $remover<T>(&mut self) -> Option<Value>
224 where
225 T: ToString,
226 {
227 self.value.remove($key_expr)
228 }
229 };
230
231 ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
232 get_set_rm_u64!($key_expr, $getter, $getter_doc);
233
234 #[doc=$setter_doc]
235 pub fn $setter<T>(&mut self, value: u64) -> Option<Value> {
236 self.value.insert(
237 String::from($key_expr),
238 Value::Number(serde_json::Number::from(value)),
239 )
240 }
241 };
242
243 ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
244 #[doc=$getter_doc]
245 pub fn $getter(&self) -> Result<Option<u64>, Error> {
246 self.value.get($key_expr).map_or_else(
247 || Ok(None),
248 |value| match value {
249 Value::Number(n) => {
250 if let Some(n) = n.as_u64() {
251 Ok(Some(n))
252 } else {
253 Err(Error::UnexpectedType)
254 }
255 }
256 _ => Err(Error::UnexpectedType),
257 },
258 )
259 }
260 };
261}
262
263macro_rules! get_ref_get_ref_mut_set_rm_obj {
264 ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr,
265 $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr,
266 $setter:ident, $setter_type:ty, $setter_doc:expr,
267 $remover:ident, $remover_doc:expr
268 ) => {
269 get_ref_get_ref_mut_set_rm_obj!(
270 $key_expr,
271 $getter_ref,
272 $getter_ref_type,
273 $getter_ref_new,
274 $getter_ref_doc
275 );
276
277 #[doc=$getter_ref_mut_doc]
278 pub fn $getter_ref_mut(&mut self) -> Result<Option<$getter_ref_mut_type>, Error> {
279 self.value.get_mut($key_expr).map_or_else(
280 || Ok(None),
281 |value| match value {
282 Value::Object(obj) => Ok(Some($getter_ref_mut_new(obj))),
283 _ => Err(Error::UnexpectedType),
284 },
285 )
286 }
287
288 #[doc=$setter_doc]
289 pub fn $setter(&mut self, value: $setter_type) -> Option<Value> {
290 self.value
291 .insert(String::from($key_expr), Value::Object(value.value))
292 }
293
294 #[doc=$remover_doc]
295 pub fn $remover(&mut self) -> Option<Value> {
296 self.value.remove($key_expr)
297 }
298 };
299 ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr) => {
300 #[doc=$getter_ref_doc]
301 pub fn $getter_ref(&self) -> Result<Option<$getter_ref_type>, Error> {
302 self.value.get($key_expr).map_or_else(
303 || Ok(None),
304 |value| match value {
305 Value::Object(obj) => Ok(Some($getter_ref_new(obj))),
306 _ => Err(Error::UnexpectedType),
307 },
308 )
309 }
310 };
311}
312
313macro_rules! get_ref_get_ref_mut_set_rm_obj_array {
314 ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr,
315 $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr,
316 $setter:ident, $setter_type:ty, $setter_doc:expr,
317 $remover:ident, $remover_doc:expr
318 ) => {
319 get_ref_get_ref_mut_set_rm_obj_array!(
320 $key_expr,
321 $getter_ref,
322 $getter_ref_type,
323 $getter_ref_new,
324 $getter_ref_doc
325 );
326
327 #[doc=$getter_ref_mut_doc]
328 pub fn $getter_ref_mut(&mut self) -> Result<Option<Vec<$getter_ref_mut_type>>, Error> {
329 self.value.get_mut($key_expr).map_or_else(
330 || Ok(None),
331 |value| match value {
332 Value::Array(arr) => arr
333 .iter_mut()
334 .map(|value| match value {
335 Value::Object(obj) => Ok($getter_ref_mut_new(obj)),
336 _ => Err(Error::UnexpectedType),
337 })
338 .collect::<Result<Vec<$getter_ref_mut_type>, Error>>()
339 .map(Some),
340 _ => Err(Error::UnexpectedType),
341 },
342 )
343 }
344
345 #[doc=$setter_doc]
346 pub fn $setter<I>(&mut self, items: I) -> Option<Value>
347 where
348 I: IntoIterator<Item = $setter_type>,
349 {
350 let items: Value =
351 Value::Array(items.into_iter().map(|a| Value::Object(a.value)).collect());
352 self.value.insert(String::from($key_expr), items)
353 }
354
355 #[doc=$remover_doc]
356 pub fn $remover(&mut self) -> Option<Value> {
357 self.value.remove($key_expr)
358 }
359 };
360 ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr) => {
361 #[doc=$getter_ref_doc]
362 pub fn $getter_ref(&self) -> Result<Option<Vec<$getter_ref_type>>, Error> {
363 self.value.get($key_expr).map_or_else(
364 || Ok(None),
365 |value| match value {
366 Value::Array(arr) => arr
367 .iter()
368 .map(|value| match value {
369 Value::Object(obj) => Ok($getter_ref_new(obj)),
370 _ => Err(Error::UnexpectedType),
371 })
372 .collect::<Result<Vec<$getter_ref_type>, Error>>()
373 .map(Some),
374 _ => Err(Error::UnexpectedType),
375 },
376 )
377 }
378 };
379}
380
381macro_rules! json_feed_prop_decl {
382 () => {};
383 ([str_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
384 get_set_rm_str!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
385 json_feed_prop_decl!($($rest),*);
386 };
387 ([str_array_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
388 get_set_rm_str_array!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
389 json_feed_prop_decl!($($rest),*);
390 };
391 ([u64_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
392 get_set_rm_u64!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
393 json_feed_prop_decl!($($rest),*);
394 };
395 ([bool_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
396 get_set_rm_bool!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
397 json_feed_prop_decl!($($rest),*);
398 };
399 ([obj_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
400 get_ref_get_ref_mut_set_rm_obj!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc, $getter_ref_mut, $getter_ref_mut_type, $getter_ref_mut_new, $getter_ref_mut_doc, $setter, $setter_type, $setter_doc, $remover, $remover_doc);
401 json_feed_prop_decl!($($rest),*);
402 };
403 ([obj_array_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
404 get_ref_get_ref_mut_set_rm_obj_array!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc, $getter_ref_mut, $getter_ref_mut_type, $getter_ref_mut_new, $getter_ref_mut_doc, $setter, $setter_type, $setter_doc, $remover, $remover_doc);
405 json_feed_prop_decl!($($rest),*);
406 };
407}
408
409macro_rules! json_feed_prop_read_only_decl {
410 () => {};
411 ([str_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
412 get_set_rm_str!($key_expr, $getter, $getter_doc);
413 json_feed_prop_read_only_decl!($($rest),*);
414 };
415 ([str_array_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
416 get_set_rm_str_array!($key_expr, $getter, $getter_doc);
417 json_feed_prop_read_only_decl!($($rest),*);
418 };
419 ([u64_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
420 get_set_rm_u64!($key_expr, $getter, $getter_doc);
421 json_feed_prop_read_only_decl!($($rest),*);
422 };
423 ([bool_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
424 get_set_rm_bool!($key_expr, $getter, $getter_doc);
425 json_feed_prop_read_only_decl!($($rest),*);
426 };
427 ([obj_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
428 get_ref_get_ref_mut_set_rm_obj!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc);
429 json_feed_prop_read_only_decl!($($rest),*);
430 };
431 ([obj_array_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_do:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
432 get_ref_get_ref_mut_set_rm_obj_array!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc);
433 json_feed_prop_read_only_decl!($($rest),*);
434 };
435}
436
437macro_rules! trait_for_borrowed_type {
438 ($name:ident) => {
439 impl<'a> $name<'a> {
440 #[must_use]
442 pub fn as_map(&self) -> &Map<String, Value> {
443 self.value
444 }
445 }
446
447 impl<'a> AsRef<Map<String, Value>> for $name<'a> {
448 fn as_ref(&self) -> &Map<String, Value> {
449 self.value
450 }
451 }
452
453 impl<'a> core::fmt::Debug for $name<'a> {
454 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
455 f.debug_struct(stringify!($name))
456 .field("value", &self.value)
457 .finish()
458 }
459 }
460
461 impl<'a> Eq for $name<'a> {}
462
463 impl<'a> From<&'a mut Map<String, Value>> for $name<'a> {
464 fn from(value: &'a mut Map<String, Value>) -> Self {
465 Self { value }
466 }
467 }
468
469 impl<'a> PartialEq<Map<String, Value>> for $name<'a> {
470 fn eq(&self, other: &Map<String, Value>) -> bool {
471 self.value.eq(&other)
472 }
473 }
474
475 impl<'a> PartialEq<$name<'a>> for $name<'a> {
476 fn eq(&self, other: &$name<'_>) -> bool {
477 self.value.eq(&other.value)
478 }
479 }
480 };
481}
482
483macro_rules! json_feed_map_type {
484 ($owned:ident, $owned_doc:expr, $borrowed:ident, $borrowed_doc:expr, $borrowed_mut:ident, $borrowed_mut_doc:expr, $to_owned:ident,
485 $($rest:tt),*
486 ) => {
487 #[doc=$owned_doc]
488 pub struct $owned {
489 value: Map<String, Value>,
490 }
491
492 impl $owned {
493 #[must_use]
495 pub fn new() -> Self {
496 Self { value: Map::new() }
497 }
498
499 #[must_use]
501 pub fn as_map(&self) -> &Map<String, Value> {
502 &self.value
503 }
504
505 pub fn as_map_mut(&mut self) -> &mut Map<String, Value> {
507 &mut self.value
508 }
509
510 #[must_use]
512 pub fn into_inner(self) -> Map<String, Value> {
513 self.value
514 }
515
516 json_feed_prop_decl!($($rest),*);
517 }
518
519 impl AsRef<Map<String,Value>> for $owned {
520 fn as_ref(&self) -> &Map<String, Value> {
521 &self.value
522 }
523 }
524
525 impl AsMut<Map<String,Value>> for $owned {
526 fn as_mut(&mut self) -> &mut Map<String, Value> {
527 &mut self.value
528 }
529 }
530
531 impl Clone for $owned {
532 fn clone(&self) -> $owned {
533 $owned {
534 value: self.value.clone(),
535 }
536 }
537 }
538
539 impl core::fmt::Debug for $owned {
540 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
541 f.debug_struct(stringify!($owned))
542 .field("value", &self.value)
543 .finish()
544 }
545 }
546
547 impl Default for $owned {
548 fn default() -> Self {
549 Self::new()
550 }
551 }
552
553 impl Eq for $owned {}
554
555 impl From<Map<String, Value>> for $owned {
556 fn from(value: Map<String, Value>) -> Self {
557 Self {
558 value
559 }
560 }
561 }
562
563 impl PartialEq<Map<String, Value>> for $owned {
564 fn eq(&self, other: &Map<String, Value>) -> bool {
565 self.value.eq(&other)
566 }
567 }
568
569 impl PartialEq<$owned> for $owned {
570 fn eq(&self, other: &$owned) -> bool {
571 self.value.eq(&other.value)
572 }
573 }
574
575 impl serde::Serialize for $owned
576 {
577 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
578 where
579 S: serde::Serializer,
580 {
581 self.value.serialize(serializer)
582 }
583 }
584
585 impl<'de> serde::de::Deserialize<'de> for $owned {
586 #[inline]
587 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
588 where
589 D: serde::de::Deserializer<'de>,
590 {
591 let map: Map<String, Value> = Map::deserialize(deserializer)?;
592 Ok(Self { value: map })
593 }
594 }
595
596 #[doc=$borrowed_doc]
597 pub struct $borrowed<'a> {
598 value: &'a Map<String, Value>,
599 }
600
601 trait_for_borrowed_type!($borrowed);
602
603 impl<'a> $borrowed<'a> {
604 #[must_use]
606 pub fn $to_owned(&self) -> $owned {
607 $owned::from(self.value.clone())
608 }
609
610 json_feed_prop_read_only_decl!($($rest),*);
611 }
612
613 impl<'a> From<&'a Map<String, Value>> for $borrowed<'a> {
614 fn from(value: &'a Map<String, Value>) -> Self {
615 Self { value }
616 }
617 }
618
619 impl<'a> serde::Serialize for $borrowed<'a>
620 {
621 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
622 where
623 S: serde::Serializer,
624 {
625 self.value.serialize(serializer)
626 }
627 }
628
629 #[doc=$borrowed_mut_doc]
630 pub struct $borrowed_mut<'a> {
631 value: &'a mut Map<String, Value>,
632 }
633
634 trait_for_borrowed_type!($borrowed_mut);
635
636 impl<'a> $borrowed_mut<'a> {
637 pub fn as_map_mut(&mut self) -> &mut Map<String, Value> {
639 self.value
640 }
641
642 #[must_use]
644 pub fn $to_owned(&self) -> $owned {
645 $owned::from(self.value.clone())
646 }
647
648 json_feed_prop_decl!($($rest),*);
649 }
650
651 impl<'a> AsMut<Map<String, Value>> for $borrowed_mut<'a> {
652 fn as_mut(&mut self) -> &mut Map<String, Value> {
653 self.value
654 }
655 }
656
657 impl<'a> serde::Serialize for $borrowed_mut<'a>
658 {
659 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
660 where
661 S: serde::Serializer,
662 {
663 self.value.serialize(serializer)
664 }
665 }
666 };
667}
668
669json_feed_map_type!(
670 Author,
671 "An author of a feed or an item in the feed.
672
673# Valid Author
674
675An `Author` must have at least one of the `name`, `url`, or `avatar` properties set.
676",
677 AuthorRef,
678 "An `Author` implemented with a borrowed reference to a JSON object.",
679 AuthorMut,
680 "An `Author` implemented with a borrowed mutable reference to a JSON object.",
681 to_author,
682 [
683 str_prop,
684 "name",
685 name,
686 "The optional author's name.",
687 set_name,
688 "Sets the name.",
689 remove_name,
690 "Remove the name."
691 ],
692 [
693 str_prop,
694 "url",
695 url,
696 "An optional URL for a site which represents the author.",
697 set_url,
698 "Sets the URL.",
699 remove_url,
700 "Removes the URL."
701 ],
702 [
703 str_prop,
704 "avatar",
705 avatar,
706 "An optional URL for an image which represents the author.",
707 set_avatar,
708 "Sets the avatar.",
709 remove_avatar,
710 "Removes the avatar."
711 ]
712);
713
714json_feed_map_type!(
715 Hub,
716 "A subscription endpoint which can be used to receive feed update notifications.
717
718# Valid Hub
719
720A `Hub` must have both the `type` and `url` properties set.
721",
722 HubRef,
723 "A `Hub` implemented with a borrowed reference to a JSON object.",
724 HubMut,
725 "A `Hub` implemented with a borrowed mutable reference to a JSON object.",
726 to_hub,
727 [
728 str_prop,
729 "type",
730 hub_type,
731 "The required protocol which is used to subscribe with.",
732 set_hub_type,
733 "Sets the type.",
734 remove_hub_type,
735 "Removes the type."
736 ],
737 [
738 str_prop,
739 "url",
740 url,
741 "A required hub type specific URL which is used to subscribe with.",
742 set_url,
743 "Sets the URL.",
744 remove_url,
745 "Removes the URL."
746 ]
747);
748
749json_feed_map_type!(
750 Item,
751 "An item is a single object (blog post, story, etc.) in the feed list.
752
753# Valid Item
754
755An `Item` must have an `id` property set and either a `content_html` or `content_text` property set.
756",
757 ItemRef,
758 "An `Item` implemented with a borrowed reference to a JSON object.",
759 ItemMut,
760 "An `Item` implemented with a borrowed mutable reference to a JSON object.",
761 to_item,
762 [str_prop, "id", id, "A required unique identifier for an item.
763
764# Important
765
766The ID should be unique across all items which have ever appeared in the feed.
767An item with the same exact ID as another item (even if it is no longer in the
768current JSON feed `items` array) are considered the same item.
769
770# Version 1.0 Incompatibility
771
772While JSON Feed 1.0 permitted values which could be coerced into JSON strings (e.g. JSON numbers), this model supports only
773JSON strings. JSON Feed 1.1 strongly suggests to only use strings. In practice, the vast majority of feeds use strings.
774
775If you wish to support non-String IDs, you can directly access the underlying `Map` with `as_map_mut` or an equivalent method and
776read the JSON value.
777", set_id, "Sets the ID.", remove_id, "Removes the ID."],
778 [str_prop, "url", url, "The optional URL which the item represents.", set_url, "Sets the URL.", remove_url, "Removes the URL."],
779 [
780 str_prop,
781 "external_url",
782 external_url,
783 "An optional related external URL to the item.",
784 set_external_url,
785 "Sets the external URL.",
786 remove_external_url,
787 "Removes the external URL."
788 ],
789 [
790 str_prop,
791 "title",
792 title,
793 "An optional title for the item.",
794 set_title,
795 "Sets the title.",
796 remove_title,
797 "Removes the title."
798 ],
799 [
800 str_prop,
801 "content_html",
802 content_html,
803 "An optional HTML string representing the content.",
804 set_content_html,
805 "Sets the HTML content.",
806 remove_content_html,
807 "Removes the HTML content."
808 ],
809 [
810 str_prop,
811 "content_text",
812 content_text,
813 "An optional plain text string representing the content.",
814 set_content_text,
815 "Sets the plain text content.",
816 remove_content_text,
817 "Removes the plain text content."
818 ],
819 [
820 str_prop,
821 "summary",
822 summary,
823 "An optional summary of the item.",
824 set_summary,
825 "Sets the summary.",
826 remove_summary,
827 "Removes the summary."
828 ],
829 [
830 str_prop,
831 "image",
832 image,
833 "An optional URL of an image representing the item.",
834 set_image,
835 "Sets the image.",
836 remove_image,
837 "Removes the image."
838 ],
839 [
840 str_prop,
841 "banner_image",
842 banner_image,
843 "An optional URL of a banner image representing the item.",
844 set_banner_image,
845 "Sets the banner image.",
846 remove_banner_image,
847 "Removes the banner image."
848 ],
849 [
850 str_prop,
851 "date_published",
852 date_published,
853 "The date which the item was published in [RFC 3339][rfc_3339] format.
854
855[rfc_3339]: https://tools.ietf.org/html/rfc3339
856",
857 set_date_published,
858 "Sets the date published.",
859 remove_date_published,
860 "Removes the date published."
861 ],
862 [
863 str_prop,
864 "date_modified",
865 date_modified,
866 "The date which the item was modified in [RFC 3339][rfc_3339] format.
867
868[rfc_3339]: https://tools.ietf.org/html/rfc3339
869",
870 set_date_modified,
871 "Sets the date modified.",
872 remove_date_modified,
873 "Removes the date modified."
874 ],
875 [
876 obj_prop,
877 "author",
878 author,
879 AuthorRef<'_>,
880 AuthorRef::from,
881 "An optional author.
882
883# Deprecation
884
885The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
886",
887 author_mut,
888 AuthorMut<'_>,
889 AuthorMut::from,
890 "An optional author.
891
892# Deprecation
893
894The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
895",
896 set_author,
897 Author,
898 "Sets the author.",
899 remove_author,
900 "Removes the author."
901 ],
902 [
903 obj_array_prop,
904 "authors",
905 authors,
906 AuthorRef<'_>,
907 AuthorRef::from,
908 "An optional array of authors.",
909 authors_mut,
910 AuthorMut<'_>,
911 AuthorMut::from,
912 "An optional array of authors.",
913 set_authors,
914 Author,
915 "Sets the authors.",
916 remove_authors,
917 "Removes the authors."
918 ],
919 [
920 str_array_prop,
921 "tags",
922 tags,
923 "An optional array of plain text tags.",
924 set_tags,
925 "Sets the tags.",
926 remove_tags,
927 "Removes the tags."
928 ],
929 [
930 str_prop,
931 "language",
932 language,
933 "The optional language which the feed data is written in.
934
935Valid values are from [RFC 5646][rfc_5646].
936
937[rfc_5646]: https://tools.ietf.org/html/rfc5646
938",
939 set_language,
940 "Sets the language.",
941 remove_language,
942 "Removes the language."
943 ],
944 [
945 obj_array_prop,
946 "attachments",
947 attachments,
948 AttachmentRef<'_>,
949 AttachmentRef::from,
950 "An optional array of relevant resources for the item.",
951 attachments_mut,
952 AttachmentMut<'_>,
953 AttachmentMut::from,
954 "An optional array of relevant resources for the item.",
955 set_attachments,
956 Attachment,
957 "Sets the attachments.",
958 remove_attachments,
959 "Removes the attachments."
960 ]
961);
962
963json_feed_map_type!(
964 Attachment,
965 "A relevant resource for an `Item`.
966
967# Valid Attachment
968
969An `Attachment` must have both the `url` and `mime_type` properties set.
970",
971 AttachmentRef,
972 "An `Attachment` implemented with a borrowed reference to a JSON object.",
973 AttachmentMut,
974 "An `Attachment` implemented with a borrowed mutable reference to a JSON object.",
975 to_attachment,
976 [
977 str_prop,
978 "url",
979 url,
980 "The required URL for the attachment.",
981 set_url,
982 "Sets the URL.",
983 remove_url,
984 "Removes the URL."
985 ],
986 [
987 str_prop,
988 "mime_type",
989 mime_type,
990 "The required [MIME][mime] type (e.g. image/png).
991
992[mime]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
993",
994 set_mime_type,
995 "Sets the MIME type.",
996 remove_mime_type,
997 "Removes the MIME type."
998 ],
999 [
1000 str_prop,
1001 "title",
1002 title,
1003 "An optional title for the attachment.
1004
1005# Important
1006
1007Attachments with the same title are considered to be alternative representations of an attachment.
1008 ",
1009 set_title,
1010 "Sets the title.",
1011 remove_title,
1012 "Removes the title."
1013 ],
1014 [
1015 u64_prop,
1016 "size_in_bytes",
1017 size_in_bytes,
1018 "The optional size of the attachment in bytes.",
1019 set_size_in_bytes,
1020 "Sets the size in bytes.",
1021 remove_size_in_bytes,
1022 "Removes the size in bytes."
1023 ],
1024 [
1025 u64_prop,
1026 "duration_in_seconds",
1027 duration_in_seconds,
1028 "The optional duration of the content in seconds.",
1029 set_duration_in_seconds,
1030 "Sets the duration of in seconds.",
1031 remove_duration_in_seconds,
1032 "Removes the duration in seconds."
1033 ]
1034);
1035
1036json_feed_map_type!(
1037 Feed,
1038 r#"A list of items with associated metadata.
1039
1040The type provides a view into a JSON object value with accessor methods for the standard properties.
1041`Feed` owns the underlying JSON object data and provides methods to access the backing object itself
1042with `as_map`, `as_map_mut`, and `into_inner`.
1043
1044The underlying data is not guaranteed to be a valid JSON Feed.
1045
1046# Valid Feed
1047
1048A `Feed` must have the `version` set to a valid JSON Feed version value, the `title` property set, and the `items`
1049property set.
1050
1051# Example
1052
1053```
1054use json_feed_model::{Feed};
1055# fn main() -> Result<(), json_feed_model::Error> {
1056let json = serde_json::json!({
1057 "version": "https://jsonfeed.org/version/1.1",
1058 "title": "Lorem ipsum dolor sit amet.",
1059 "home_page_url": "https://example.org/",
1060 "feed_url": "https://example.org/feed.json",
1061 "items": [
1062 {
1063 "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0",
1064 "content_text": "Aenean tristique dictum mauris, et.",
1065 "url": "https://example.org/aenean-tristique"
1066 },
1067 {
1068 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1069 "content_html": "Vestibulum non magna vitae tortor.",
1070 "url": "https://example.org/vestibulum-non"
1071 }
1072 ]
1073});
1074let feed = json_feed_model::from_value(json).unwrap();
1075assert_eq!(feed.version()?, Some(json_feed_model::VERSION_1_1));
1076assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1077assert_eq!(feed.home_page_url()?, Some("https://example.org/"));
1078assert_eq!(feed.feed_url()?, Some("https://example.org/feed.json"));
1079
1080let items = feed.items()?;
1081let items = items.unwrap();
1082assert_eq!(items.len(), 2);
1083
1084assert_eq!(items[0].id()?, Some("cd7f0673-8e81-4e13-b273-4bd1b83967d0"));
1085assert_eq!(
1086 items[0].content_text()?,
1087 Some("Aenean tristique dictum mauris, et.")
1088);
1089assert_eq!(
1090 items[0].url()?,
1091 Some("https://example.org/aenean-tristique")
1092);
1093
1094assert_eq!(items[1].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1095assert_eq!(
1096 items[1].content_html()?,
1097 Some("Vestibulum non magna vitae tortor.")
1098);
1099assert_eq!(items[1].url()?, Some("https://example.org/vestibulum-non"));
1100# Ok(())
1101# }
1102```
1103 "#,
1104 FeedRef,
1105 "A `Feed` implemented with a borrowed reference to a JSON object.",
1106 FeedMut,
1107 "A `Feed` implemented with a borrowed mutable reference to a JSON object.",
1108 to_feed,
1109 [
1110 str_prop,
1111 "version",
1112 version,
1113 "The required URL formatted version identifier.
1114
1115Identifies what version of the spec the feed is suppose to be compliant with.",
1116 set_version,
1117 "Sets the version identifier.",
1118 remove_version,
1119 "Removes the version identifier."
1120 ],
1121 [
1122 str_prop,
1123 "title",
1124 title,
1125 "The optional name of the feed.",
1126 set_title,
1127 "Sets the name of the feed.",
1128 remove_title,
1129 "Removes the name of the feed."
1130 ],
1131 [
1132 str_prop,
1133 "home_page_url",
1134 home_page_url,
1135 "The optional URL which the feed is suppose to represent.",
1136 set_home_page_url,
1137 "Sets the home page URL.",
1138 remove_home_page_url,
1139 "Removes the home page URL."
1140 ],
1141 [
1142 str_prop,
1143 "feed_url",
1144 feed_url,
1145 "The optional URL which this feed can be retrieived from.",
1146 set_feed_url,
1147 "Sets the feed URL.",
1148 remove_feed_url,
1149 "Removes the feed URL."
1150 ],
1151 [
1152 str_prop,
1153 "description",
1154 description,
1155 "An optional description of the feed.",
1156 set_description,
1157 "Sets the description of the feed.",
1158 remove_description,
1159 "Removes the description of the feed."
1160 ],
1161 [
1162 str_prop,
1163 "user_comment",
1164 user_comment,
1165 "An optional meta description about the feed only intended to be viewed in the raw JSON form.",
1166 set_user_comment,
1167 "Sets the user comment.",
1168 remove_user_comment,
1169 "Removes the user comment."
1170 ],
1171 [
1172 str_prop,
1173 "next_url",
1174 next_url,
1175 "An optional pagination URL.",
1176 set_next_url,
1177 "Sets the next URL.",
1178 remove_next_url,
1179 "Removes the next URL."
1180 ],
1181 [str_prop, "icon", icon, "An optional URL to an icon for use in a list of items.", set_icon, "Sets the icon.", remove_icon, "Removes the icon."],
1182 [
1183 str_prop,
1184 "favicon",
1185 favicon,
1186 "An optional URL to a favicon suitable for use in a list of feeds.",
1187 set_favicon,
1188 "Sets the favicon URL.",
1189 remove_favicon,
1190 "Removes the favicon URL."
1191 ],
1192 [
1193 obj_prop,
1194 "author",
1195 author,
1196 AuthorRef<'_>,
1197 AuthorRef::from,
1198 "An optional author.
1199
1200# Deprecation
1201
1202The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1203",
1204 author_mut,
1205 AuthorMut<'_>,
1206 AuthorMut::from,
1207 "An optional author.
1208
1209# Deprecation
1210
1211The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1212",
1213 set_author,
1214 Author,
1215 "Sets the author.",
1216 remove_author,
1217 "Removes the author."
1218 ],
1219 [
1220 obj_array_prop,
1221 "authors",
1222 authors,
1223 AuthorRef<'_>,
1224 AuthorRef::from,
1225 "An optional array of authors.",
1226 authors_mut,
1227 AuthorMut<'_>,
1228 AuthorMut::from,
1229 "An optional array of authors.",
1230 set_authors,
1231 Author,
1232 "Sets the authors.",
1233 remove_authors,
1234 "Removes the authors."
1235 ],
1236 [
1237 str_prop,
1238 "language",
1239 language,
1240 "The optional language which the feed data is written in.
1241
1242Valid values are from [RFC 5646][rfc_5646].
1243
1244[rfc_5646]: https://tools.ietf.org/html/rfc5646
1245",
1246 set_language,
1247 "Sets the language.",
1248 remove_language,
1249 "Removes the language."
1250 ],
1251 [
1252 bool_prop,
1253 "expired",
1254 expired,
1255 "Optionally determines if the feed will be updated in the future.
1256
1257If true, the feed will not be updated in the future. If false or `None`, then the feed may be updated in the future.",
1258 set_expired,
1259 "Sets the expired flag.",
1260 remove_expired,
1261 "Removes the expired flag."
1262 ],
1263 [
1264 obj_array_prop,
1265 "hubs",
1266 hubs,
1267 HubRef<'_>,
1268 HubRef::from,
1269 "Optional subscription endpoints which can be used to received feed update notifications.",
1270 hubs_mut,
1271 HubMut<'_>,
1272 HubMut::from,
1273 "Subscription endpoints which can be used to received feed update notifications.",
1274 set_hubs,
1275 Hub,
1276 "Sets the hubs.",
1277 remove_hubs,
1278 "Removes the hubs."
1279 ],
1280 [
1281 obj_array_prop,
1282 "items",
1283 items,
1284 ItemRef<'_>,
1285 ItemRef::from,
1286 "A required array of `Items`.",
1287 items_mut,
1288 ItemMut<'_>,
1289 ItemMut::from,
1290 "A required array of `Items`.",
1291 set_items,
1292 Item,
1293 "Sets the items.",
1294 remove_items,
1295 "Removes the items."
1296 ]
1297);
1298
1299fn is_extension_key(key: &str) -> bool {
1300 key.as_bytes().iter().next() == Some(&b'_')
1301}
1302
1303fn are_keys_valid<'a, I>(keys: I, valid_keys: &BTreeSet<&str>) -> bool
1304where
1305 I: IntoIterator<Item = &'a String>,
1306{
1307 keys.into_iter()
1308 .all(|k| valid_keys.contains(k.as_str()) || is_extension_key(k))
1309}
1310
1311fn is_valid_attachment(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1312 match version {
1313 Version::Unknown(_) => return false,
1314 Version::Version1 | Version::Version1_1 => {}
1315 }
1316 let attachment_ref = AttachmentRef::from(map);
1317 let mut valid_keys = BTreeSet::new();
1318 valid_keys.insert("url");
1319 valid_keys.insert("mime_type");
1320 valid_keys.insert("title");
1321 valid_keys.insert("size_in_bytes");
1322 valid_keys.insert("duration_in_seconds");
1323
1324 attachment_ref.url().map_or(false, |url| url.is_some())
1325 && attachment_ref
1326 .mime_type()
1327 .map_or(false, |mime_type| mime_type.is_some())
1328 && attachment_ref.title().is_ok()
1329 && attachment_ref.size_in_bytes().is_ok()
1330 && attachment_ref.duration_in_seconds().is_ok()
1331 && are_keys_valid(map.keys(), &valid_keys)
1332}
1333
1334impl Attachment {
1335 #[must_use]
1337 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1338 is_valid_attachment(&self.value, version)
1339 }
1340}
1341
1342impl<'a> AttachmentMut<'a> {
1343 #[must_use]
1345 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1346 is_valid_attachment(self.value, version)
1347 }
1348}
1349
1350impl<'a> AttachmentRef<'a> {
1351 #[must_use]
1353 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1354 is_valid_attachment(self.value, version)
1355 }
1356}
1357
1358fn is_valid_author(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1359 match version {
1360 Version::Unknown(_) => return false,
1361 Version::Version1 | Version::Version1_1 => {}
1362 }
1363 let author_ref = AuthorRef::from(map);
1364 let mut valid_keys = BTreeSet::new();
1365 valid_keys.insert("name");
1366 valid_keys.insert("avatar");
1367 valid_keys.insert("url");
1368
1369 let name_result = author_ref.name();
1370 let avatar_result = author_ref.avatar();
1371 let url_result = author_ref.url();
1372
1373 name_result.is_ok()
1374 && avatar_result.is_ok()
1375 && url_result.is_ok()
1376 && (name_result.map_or(false, |name| name.is_some())
1377 || avatar_result.map_or(false, |avatar| avatar.is_some())
1378 || url_result.map_or(false, |url| url.is_some()))
1379 && are_keys_valid(map.keys(), &valid_keys)
1380}
1381
1382impl Author {
1383 #[must_use]
1385 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1386 is_valid_author(&self.value, version)
1387 }
1388}
1389
1390impl<'a> AuthorMut<'a> {
1391 #[must_use]
1393 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1394 is_valid_author(self.value, version)
1395 }
1396}
1397
1398impl<'a> AuthorRef<'a> {
1399 #[must_use]
1401 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1402 is_valid_author(self.value, version)
1403 }
1404}
1405
1406fn is_valid_feed(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1407 match version {
1408 Version::Unknown(_) => return false,
1409 Version::Version1 | Version::Version1_1 => {}
1410 }
1411 let feed_ref = FeedRef::from(map);
1412 let mut valid_keys = BTreeSet::new();
1413 valid_keys.insert("version");
1414 valid_keys.insert("title");
1415 valid_keys.insert("home_page_url");
1416 valid_keys.insert("feed_url");
1417 valid_keys.insert("description");
1418 valid_keys.insert("user_comment");
1419 valid_keys.insert("next_url");
1420 valid_keys.insert("favicon");
1421 valid_keys.insert("author");
1422 match version {
1423 Version::Version1_1 => {
1424 valid_keys.insert("authors");
1425 valid_keys.insert("language");
1426 }
1427 Version::Version1 | Version::Unknown(_) => {}
1428 }
1429 valid_keys.insert("expired");
1430 valid_keys.insert("hubs");
1431 valid_keys.insert("items");
1432
1433 feed_ref.version().map_or(false, |v| {
1434 v.map_or(false, |v| match Version::from(v) {
1435 Version::Unknown(_) => false,
1436 Version::Version1 => match version {
1437 Version::Version1 | Version::Version1_1 => true,
1438 Version::Unknown(_) => false,
1439 },
1440 Version::Version1_1 => match version {
1441 Version::Version1 | Version::Unknown(_) => false,
1442 Version::Version1_1 => true,
1443 },
1444 })
1445 }) && feed_ref
1446 .title()
1447 .map_or_else(|_| false, |title| title.is_some())
1448 && feed_ref.items().map_or(false, |items| {
1449 items.map_or(false, |items| {
1450 items.iter().all(|item| item.is_valid(version))
1451 })
1452 })
1453 && feed_ref.hubs().map_or(false, |hubs| {
1454 hubs.map_or(true, |hubs| hubs.iter().all(|hub| hub.is_valid(version)))
1455 })
1456 && feed_ref.home_page_url().is_ok()
1457 && feed_ref.feed_url().is_ok()
1458 && feed_ref.description().is_ok()
1459 && feed_ref.user_comment().is_ok()
1460 && feed_ref.next_url().is_ok()
1461 && feed_ref.icon().is_ok()
1462 && feed_ref.favicon().is_ok()
1463 && feed_ref.author().is_ok()
1464 && feed_ref.authors().is_ok()
1465 && feed_ref.language().is_ok()
1466 && feed_ref.expired().is_ok()
1467 && are_keys_valid(map.keys(), &valid_keys)
1468}
1469
1470impl Feed {
1471 #[must_use]
1473 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1474 is_valid_feed(&self.value, version)
1475 }
1476}
1477
1478impl<'a> FeedMut<'a> {
1479 #[must_use]
1481 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1482 is_valid_feed(self.value, version)
1483 }
1484}
1485
1486impl<'a> FeedRef<'a> {
1487 #[must_use]
1489 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1490 is_valid_feed(self.value, version)
1491 }
1492}
1493
1494fn is_valid_hub(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1495 match version {
1496 Version::Unknown(_) => return false,
1497 Version::Version1 | Version::Version1_1 => {}
1498 }
1499 let hub_ref = HubRef::from(map);
1500 let mut valid_keys = BTreeSet::new();
1501 valid_keys.insert("type");
1502 valid_keys.insert("url");
1503
1504 hub_ref.url().map_or(false, |url| url.is_some())
1505 && hub_ref
1506 .hub_type()
1507 .map_or(false, |hub_type| hub_type.is_some())
1508 && are_keys_valid(map.keys(), &valid_keys)
1509}
1510
1511impl Hub {
1512 #[must_use]
1514 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1515 is_valid_hub(&self.value, version)
1516 }
1517}
1518
1519impl<'a> HubMut<'a> {
1520 #[must_use]
1522 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1523 is_valid_hub(self.value, version)
1524 }
1525}
1526
1527impl<'a> HubRef<'a> {
1528 #[must_use]
1530 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1531 is_valid_hub(self.value, version)
1532 }
1533}
1534
1535fn is_valid_item(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1536 match version {
1537 Version::Unknown(_) => return false,
1538 Version::Version1 | Version::Version1_1 => {}
1539 }
1540 let item_ref = ItemRef::from(map);
1541 let mut valid_keys = BTreeSet::new();
1542 valid_keys.insert("id");
1543 valid_keys.insert("url");
1544 valid_keys.insert("external_url");
1545 valid_keys.insert("title");
1546 valid_keys.insert("content_html");
1547 valid_keys.insert("content_text");
1548 valid_keys.insert("summary");
1549 valid_keys.insert("image");
1550 valid_keys.insert("banner_image");
1551 valid_keys.insert("date_published");
1552 valid_keys.insert("date_modified");
1553 valid_keys.insert("author");
1554 match version {
1555 Version::Version1_1 => {
1556 valid_keys.insert("authors");
1557 valid_keys.insert("language");
1558 }
1559 Version::Version1 | Version::Unknown(_) => {}
1560 }
1561 valid_keys.insert("tags");
1562 valid_keys.insert("attachments");
1563
1564 let content_html_result = item_ref.content_html();
1565 let content_text_result = item_ref.content_text();
1566
1567 item_ref.id().map_or(false, |id| id.is_some())
1568 && item_ref.authors().map_or(false, |authors| {
1569 authors.map_or(true, |authors| {
1570 authors.iter().all(|author| author.is_valid(version))
1571 })
1572 })
1573 && item_ref.attachments().map_or(false, |attachments| {
1574 attachments.map_or(true, |attachments| {
1575 attachments
1576 .iter()
1577 .all(|attachment| attachment.is_valid(version))
1578 })
1579 })
1580 && item_ref.id().is_ok()
1581 && item_ref.url().is_ok()
1582 && item_ref.external_url().is_ok()
1583 && item_ref.title().is_ok()
1584 && content_html_result.is_ok()
1585 && content_text_result.is_ok()
1586 && (content_text_result.map_or(false, |content| content.is_some())
1587 || content_html_result.map_or(false, |content| content.is_some()))
1588 && item_ref.summary().is_ok()
1589 && item_ref.image().is_ok()
1590 && item_ref.banner_image().is_ok()
1591 && item_ref.date_published().is_ok()
1592 && item_ref.date_modified().is_ok()
1593 && item_ref.author().is_ok()
1594 && item_ref.tags().is_ok()
1595 && item_ref.language().is_ok()
1596 && are_keys_valid(map.keys(), &valid_keys)
1597}
1598
1599impl Item {
1600 #[must_use]
1602 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1603 is_valid_item(&self.value, version)
1604 }
1605}
1606
1607impl<'a> ItemMut<'a> {
1608 #[must_use]
1610 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1611 is_valid_item(self.value, version)
1612 }
1613}
1614
1615impl<'a> ItemRef<'a> {
1616 #[must_use]
1618 pub fn is_valid(&self, version: &Version<'_>) -> bool {
1619 is_valid_item(self.value, version)
1620 }
1621}
1622
1623#[cfg(feature = "std")]
1631pub fn from_reader<R>(reader: R) -> Result<Feed, Error>
1632where
1633 R: std::io::Read,
1634{
1635 let value = serde_json::from_reader(reader)?;
1636 from_value(value)
1637}
1638
1639pub fn from_str(s: &str) -> Result<Feed, Error> {
1647 from_slice(s.as_bytes())
1648}
1649
1650pub fn from_slice(v: &[u8]) -> Result<Feed, Error> {
1658 let value = serde_json::from_slice(v)?;
1659 from_value(value)
1660}
1661
1662pub fn from_value(value: Value) -> Result<Feed, Error> {
1685 match value {
1686 Value::Object(obj) => Ok(Feed { value: obj }),
1687 _ => Err(Error::UnexpectedType),
1688 }
1689}
1690
1691#[cfg(test)]
1692mod tests {
1693 use super::*;
1694 #[cfg(all(feature = "alloc", not(feature = "std")))]
1695 use alloc::vec;
1696
1697 #[test]
1698 fn simple_example() -> Result<(), Error> {
1699 let json = serde_json::json!({
1700 "version": "https://jsonfeed.org/version/1.1",
1701 "title": "Lorem ipsum dolor sit amet.",
1702 "home_page_url": "https://example.org/",
1703 "feed_url": "https://example.org/feed.json",
1704 "items": [
1705 {
1706 "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0",
1707 "content_text": "Aenean tristique dictum mauris, et.",
1708 "url": "https://example.org/aenean-tristique"
1709 },
1710 {
1711 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1712 "content_html": "Vestibulum non magna vitae tortor.",
1713 "url": "https://example.org/vestibulum-non"
1714 }
1715 ]
1716 });
1717
1718 let feed = from_value(json)?;
1719
1720 assert!(feed.is_valid(&Version::Version1_1));
1721
1722 assert_eq!(feed.version()?, Some(VERSION_1_1));
1723 assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1724 assert_eq!(feed.home_page_url()?, Some("https://example.org/"));
1725 assert_eq!(feed.feed_url()?, Some("https://example.org/feed.json"));
1726
1727 let items: Option<Vec<ItemRef<'_>>> = feed.items()?;
1728 assert!(items.is_some());
1729 let items: Vec<ItemRef<'_>> = items.unwrap();
1730 assert_eq!(items.len(), 2);
1731
1732 assert_eq!(items[0].id()?, Some("cd7f0673-8e81-4e13-b273-4bd1b83967d0"));
1733 assert_eq!(
1734 items[0].content_text()?,
1735 Some("Aenean tristique dictum mauris, et.")
1736 );
1737 assert_eq!(
1738 items[0].url()?,
1739 Some("https://example.org/aenean-tristique")
1740 );
1741
1742 assert_eq!(items[1].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1743 assert_eq!(
1744 items[1].content_html()?,
1745 Some("Vestibulum non magna vitae tortor.")
1746 );
1747 assert_eq!(items[1].url()?, Some("https://example.org/vestibulum-non"));
1748
1749 Ok(())
1750 }
1751
1752 #[test]
1753 fn read_extensions() -> Result<(), Error> {
1754 let json = serde_json::json!({
1755 "version": "https://jsonfeed.org/version/1.1",
1756 "title": "Lorem ipsum dolor sit amet.",
1757 "_example": {
1758 "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0"
1759 },
1760 "items": [
1761 {
1762 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1763 "content_html": "Vestibulum non magna vitae tortor.",
1764 "url": "https://example.org/vestibulum-non",
1765 "_extension": 1
1766 }
1767 ]
1768 });
1769 let feed = from_value(json).unwrap();
1770
1771 assert!(feed.is_valid(&Version::Version1_1));
1772
1773 assert_eq!(feed.version()?, Some(VERSION_1_1));
1774 assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1775
1776 let example_value = feed.as_map().get("_example");
1777 assert_eq!(
1778 example_value,
1779 Some(&serde_json::json!({ "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0" }))
1780 );
1781
1782 let items = feed.items()?;
1783 let items = items.unwrap();
1784 assert_eq!(items.len(), 1);
1785
1786 assert_eq!(items[0].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1787 assert_eq!(
1788 items[0].content_html()?,
1789 Some("Vestibulum non magna vitae tortor.")
1790 );
1791 assert_eq!(items[0].url()?, Some("https://example.org/vestibulum-non"));
1792
1793 let extension_value = items[0].as_map().get("_extension");
1794 assert_eq!(extension_value, Some(&serde_json::json!(1)));
1795
1796 Ok(())
1797 }
1798
1799 #[test]
1800 fn write_extensions() -> Result<(), Error> {
1801 let mut feed = Feed::new();
1802 feed.set_version(Version::Version1_1);
1803 feed.set_title("Lorem ipsum dolor sit amet.");
1804 feed.as_map_mut().insert(
1805 String::from("_example"),
1806 serde_json::json!({ "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0" }),
1807 );
1808
1809 let mut item = Item::new();
1810 item.set_id("invalid-id");
1811 item.set_content_html("Vestibulum non magna vitae tortor.");
1812 item.set_url("https://example.org/vestibulum-non");
1813 item.as_map_mut()
1814 .insert(String::from("_extension"), serde_json::json!(1));
1815
1816 let items = vec![item];
1817 feed.set_items(items);
1818
1819 let item = &mut feed.items_mut()?.unwrap()[0];
1820 item.set_id("2bcb497d-c40b-4493-b5ae-bc63c74b48fa");
1821
1822 assert!(feed.is_valid(&Version::Version1_1));
1823
1824 let expected_json = serde_json::json!({
1825 "version": "https://jsonfeed.org/version/1.1",
1826 "title": "Lorem ipsum dolor sit amet.",
1827 "_example": {
1828 "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0"
1829 },
1830 "items": [
1831 {
1832 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1833 "content_html": "Vestibulum non magna vitae tortor.",
1834 "url": "https://example.org/vestibulum-non",
1835 "_extension": 1
1836 }
1837 ]
1838 });
1839 assert_eq!(feed, from_value(expected_json.clone())?);
1840 assert_eq!(serde_json::to_value(feed.clone())?, expected_json);
1841
1842 let output = serde_json::to_string(&feed);
1843 assert!(output.is_ok());
1844
1845 Ok(())
1846 }
1847
1848 #[test]
1849 fn is_valid_version_forward_compatible() {
1850 let json = serde_json::json!({
1851 "version": "https://jsonfeed.org/version/1",
1852 "title": "Lorem ipsum dolor sit amet.",
1853 "items": [
1854 {
1855 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1856 "content_html": "Vestibulum non magna vitae tortor.",
1857 "url": "https://example.org/vestibulum-non",
1858 }
1859 ]
1860 });
1861 let feed = from_value(json).unwrap();
1862
1863 assert!(feed.is_valid(&Version::Version1_1));
1864 assert!(feed.is_valid(&Version::Version1));
1865 }
1866
1867 #[test]
1868 fn is_valid_version_backward_compatible() {
1869 let json = serde_json::json!({
1870 "version": "https://jsonfeed.org/version/1.1",
1871 "title": "Lorem ipsum dolor sit amet.",
1872 "items": [
1873 {
1874 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1875 "content_html": "Vestibulum non magna vitae tortor.",
1876 "url": "https://example.org/vestibulum-non",
1877 }
1878 ]
1879 });
1880 let feed = from_value(json).unwrap();
1881
1882 assert!(feed.is_valid(&Version::Version1_1));
1883 assert!(!feed.is_valid(&Version::Version1));
1884 }
1885
1886 #[test]
1887 fn custom_extension_trait() -> Result<(), Error> {
1888 trait ExampleExtension {
1889 fn example(&self) -> Result<Option<&str>, Error>;
1890
1891 fn set_example<T>(&mut self, value: T) -> Option<Value>
1892 where
1893 T: ToString;
1894 }
1895
1896 impl ExampleExtension for Feed {
1897 fn example(&self) -> Result<Option<&str>, Error> {
1898 self.as_map().get("_example").map_or_else(
1899 || Ok(None),
1900 |value| match value {
1901 Value::String(s) => Ok(Some(s.as_str())),
1902 _ => Err(Error::UnexpectedType),
1903 },
1904 )
1905 }
1906
1907 fn set_example<T>(&mut self, value: T) -> Option<Value>
1908 where
1909 T: ToString,
1910 {
1911 self.as_map_mut()
1912 .insert(String::from("_example"), Value::String(value.to_string()))
1913 }
1914 }
1915
1916 let mut feed = Feed::new();
1917 feed.set_version(Version::Version1_1);
1918 feed.set_title("Lorem ipsum dolor sit amet.");
1919
1920 feed.set_example("123456");
1921
1922 let mut item = Item::new();
1923 item.set_id("2bcb497d-c40b-4493-b5ae-bc63c74b48fa");
1924 item.set_content_text("Vestibulum non magna vitae tortor.");
1925 item.set_url("https://example.org/vestibulum-non");
1926
1927 feed.set_items(vec![item]);
1928
1929 assert!(feed.is_valid(&Version::Version1_1));
1930
1931 let expected_json = serde_json::json!({
1932 "version": "https://jsonfeed.org/version/1.1",
1933 "title": "Lorem ipsum dolor sit amet.",
1934 "_example": "123456",
1935 "items": [
1936 {
1937 "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1938 "content_text": "Vestibulum non magna vitae tortor.",
1939 "url": "https://example.org/vestibulum-non",
1940 }
1941 ]
1942 });
1943 assert_eq!(feed, from_value(expected_json)?);
1944
1945 assert_eq!(feed.example()?, Some("123456"));
1946
1947 let output = serde_json::to_string(&feed);
1948 assert!(output.is_ok());
1949
1950 Ok(())
1951 }
1952}