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