add jsonfeed library
This commit is contained in:
parent
4085fb2e4f
commit
d8a16e0d51
|
@ -21,6 +21,21 @@ dependencies = [
|
||||||
"pretty",
|
"pretty",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -86,6 +101,20 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -373,6 +402,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-chain"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fake-simd"
|
name = "fake-simd"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -550,6 +588,12 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -756,6 +800,16 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonfeed"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"error-chain",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel32-sys"
|
name = "kernel32-sys"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -905,6 +959,15 @@ dependencies = [
|
||||||
"unicase 2.6.0",
|
"unicase 2.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.6.22"
|
version = "0.6.22"
|
||||||
|
@ -1023,6 +1086,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1557,6 +1626,12 @@ dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
|
|
@ -31,3 +31,7 @@ warp = "0.2"
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = { version = "0.11", features = ["warp02"] }
|
ructe = { version = "0.11", features = ["warp02"] }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"./lib/jsonfeed"
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
*.html
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
authors = ["Paul Woolcock <paul@woolcock.us>"]
|
||||||
|
description = "Parser for the JSONFeed (http://jsonfeed.org) specification\n"
|
||||||
|
documentation = "https://docs.rs/jsonfeed"
|
||||||
|
homepage = "https://github.com/pwoolcoc/jsonfeed"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
name = "jsonfeed"
|
||||||
|
readme = "README.adoc"
|
||||||
|
version = "0.2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
error-chain = "0.10.0"
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
serde_json = "1"
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2014 The Rust Project Developers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,27 @@
|
||||||
|
= JSON Feed Parser
|
||||||
|
|
||||||
|
[link=https://github.com/pwoolcoc/jsonfeed]
|
||||||
|
image::https://img.shields.io/crates/v/jsonfeed.svg[JSON Feed crate version]
|
||||||
|
|
||||||
|
This is a http://jsonfeed.org[JSON Feed] parser in Rust. Just a thin layer on top of `serde`, but it
|
||||||
|
provides serialization & deserialization, along with a Builder API for constructing feeds.
|
||||||
|
|
||||||
|
Note that this is alpha, I still need to add a lot of tests and a couple more features.
|
||||||
|
|
||||||
|
== Example
|
||||||
|
|
||||||
|
----
|
||||||
|
extern crate jsonfeed;
|
||||||
|
extern crate reqwest;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let resp = reqwest::get("https://example.com/feed.json").unwrap();
|
||||||
|
let feed = jsonfeed::from_reader(resp).unwrap();
|
||||||
|
println!("Feed title is: {}", feed.title);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
* Tests. Lots and lots of tests
|
||||||
|
* Implement ability to add, serialize, and deserialize custom attributes from the json feed spec
|
|
@ -0,0 +1,120 @@
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use errors::*;
|
||||||
|
use feed::{Feed, Author, Attachment};
|
||||||
|
use item::{Content, Item};
|
||||||
|
|
||||||
|
/// Feed Builder
|
||||||
|
///
|
||||||
|
/// This is used to programmatically build up a Feed object,
|
||||||
|
/// which can be serialized later into a JSON string
|
||||||
|
pub struct Builder(Feed);
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
pub fn new() -> Builder {
|
||||||
|
Builder(Feed::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title<I: Into<String>>(mut self, t: I) -> Builder {
|
||||||
|
self.0.title = t.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item(mut self, item: Item) -> Builder {
|
||||||
|
self.0.items.push(item);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Feed {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Builder object for an item in a feed
|
||||||
|
pub struct ItemBuilder {
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub external_url: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub content: Option<Content>,
|
||||||
|
pub summary: Option<String>,
|
||||||
|
pub image: Option<String>,
|
||||||
|
pub banner_image: Option<String>,
|
||||||
|
pub date_published: Option<String>,
|
||||||
|
pub date_modified: Option<String>,
|
||||||
|
pub author: Option<Author>,
|
||||||
|
pub tags: Option<Vec<String>>,
|
||||||
|
pub attachments: Option<Vec<Attachment>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemBuilder {
|
||||||
|
pub fn new() -> ItemBuilder {
|
||||||
|
ItemBuilder {
|
||||||
|
id: None,
|
||||||
|
url: None,
|
||||||
|
external_url: None,
|
||||||
|
title: None,
|
||||||
|
content: None,
|
||||||
|
summary: None,
|
||||||
|
image: None,
|
||||||
|
banner_image: None,
|
||||||
|
date_published: None,
|
||||||
|
date_modified: None,
|
||||||
|
author: None,
|
||||||
|
tags: None,
|
||||||
|
attachments: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title<I: Into<String>>(mut self, i: I) -> ItemBuilder {
|
||||||
|
self.title = Some(i.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_html<I: Into<String>>(mut self, i: I) -> ItemBuilder {
|
||||||
|
match self.content {
|
||||||
|
Some(Content::Text(t)) => {
|
||||||
|
self.content = Some(Content::Both(i.into(), t));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.content = Some(Content::Html(i.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_text<I: Into<String>>(mut self, i: I) -> ItemBuilder {
|
||||||
|
match self.content {
|
||||||
|
Some(Content::Html(s)) => {
|
||||||
|
self.content = Some(Content::Both(s, i.into()));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.content = Some(Content::Text(i.into()));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Result<Item> {
|
||||||
|
if self.id.is_none() || self.content.is_none() {
|
||||||
|
return Err("missing field 'id' or 'content_*'".into());
|
||||||
|
}
|
||||||
|
Ok(Item {
|
||||||
|
id: self.id.unwrap(),
|
||||||
|
url: self.url,
|
||||||
|
external_url: self.external_url,
|
||||||
|
title: self.title,
|
||||||
|
content: self.content.unwrap(),
|
||||||
|
summary: self.summary,
|
||||||
|
image: self.image,
|
||||||
|
banner_image: self.banner_image,
|
||||||
|
date_published: self.date_published,
|
||||||
|
date_modified: self.date_modified,
|
||||||
|
author: self.author,
|
||||||
|
tags: self.tags,
|
||||||
|
attachments: self.attachments
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
use serde_json;
|
||||||
|
error_chain!{
|
||||||
|
foreign_links {
|
||||||
|
Serde(serde_json::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,296 @@
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use item::Item;
|
||||||
|
use builder::Builder;
|
||||||
|
|
||||||
|
const VERSION_1: &'static str = "https://jsonfeed.org/version/1";
|
||||||
|
|
||||||
|
/// Represents a single feed
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // Serialize a feed object to a JSON string
|
||||||
|
///
|
||||||
|
/// # extern crate jsonfeed;
|
||||||
|
/// # use std::default::Default;
|
||||||
|
/// # use jsonfeed::Feed;
|
||||||
|
/// # fn main() {
|
||||||
|
/// let feed: Feed = Feed::default();
|
||||||
|
/// assert_eq!(
|
||||||
|
/// jsonfeed::to_string(&feed).unwrap(),
|
||||||
|
/// "{\"version\":\"https://jsonfeed.org/version/1\",\"title\":\"\",\"items\":[]}"
|
||||||
|
/// );
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// // Deserialize a feed objects from a JSON String
|
||||||
|
///
|
||||||
|
/// # extern crate jsonfeed;
|
||||||
|
/// # use jsonfeed::Feed;
|
||||||
|
/// # fn main() {
|
||||||
|
/// let json = "{\"version\":\"https://jsonfeed.org/version/1\",\"title\":\"\",\"items\":[]}";
|
||||||
|
/// let feed: Feed = jsonfeed::from_str(&json).unwrap();
|
||||||
|
/// assert_eq!(
|
||||||
|
/// feed,
|
||||||
|
/// Feed::default()
|
||||||
|
/// );
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Feed {
|
||||||
|
pub version: String,
|
||||||
|
pub title: String,
|
||||||
|
pub items: Vec<Item>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub home_page_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub feed_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user_comment: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub next_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub icon: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub favicon: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub author: Option<Author>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub expired: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub hubs: Option<Vec<Hub>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Feed {
|
||||||
|
/// Used to construct a Feed object
|
||||||
|
pub fn builder() -> Builder {
|
||||||
|
Builder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Feed {
|
||||||
|
fn default() -> Feed {
|
||||||
|
Feed {
|
||||||
|
version: VERSION_1.to_string(),
|
||||||
|
title: "".to_string(),
|
||||||
|
items: vec![],
|
||||||
|
home_page_url: None,
|
||||||
|
feed_url: None,
|
||||||
|
description: None,
|
||||||
|
user_comment: None,
|
||||||
|
next_url: None,
|
||||||
|
icon: None,
|
||||||
|
favicon: None,
|
||||||
|
author: None,
|
||||||
|
expired: None,
|
||||||
|
hubs: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an `attachment` for an item
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Attachment {
|
||||||
|
url: String,
|
||||||
|
mime_type: String,
|
||||||
|
title: Option<String>,
|
||||||
|
size_in_bytes: Option<u64>,
|
||||||
|
duration_in_seconds: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an `author` in both a feed and a feed item
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Author {
|
||||||
|
name: Option<String>,
|
||||||
|
url: Option<String>,
|
||||||
|
avatar: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Author {
|
||||||
|
pub fn new() -> Author {
|
||||||
|
Author {
|
||||||
|
name: None,
|
||||||
|
url: None,
|
||||||
|
avatar: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name<I: Into<String>>(mut self, name: I) -> Self {
|
||||||
|
self.name = Some(name.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn url<I: Into<String>>(mut self, url: I) -> Self {
|
||||||
|
self.url = Some(url.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn avatar<I: Into<String>>(mut self, avatar: I) -> Self {
|
||||||
|
self.avatar = Some(avatar.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a `hub` for a feed
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Hub {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: String,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json;
|
||||||
|
use std::default::Default;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_feed() {
|
||||||
|
let feed = Feed {
|
||||||
|
version: "https://jsonfeed.org/version/1".to_string(),
|
||||||
|
title: "some title".to_string(),
|
||||||
|
items: vec![],
|
||||||
|
home_page_url: None,
|
||||||
|
description: None,
|
||||||
|
expired: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&feed).unwrap(),
|
||||||
|
r#"{"version":"https://jsonfeed.org/version/1","title":"some title","items":[],"expired":true}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_feed() {
|
||||||
|
let json = r#"{"version":"https://jsonfeed.org/version/1","title":"some title","items":[]}"#;
|
||||||
|
let feed: Feed = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Feed {
|
||||||
|
version: "https://jsonfeed.org/version/1".to_string(),
|
||||||
|
title: "some title".to_string(),
|
||||||
|
items: vec![],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
feed,
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_attachment() {
|
||||||
|
let attachment = Attachment {
|
||||||
|
url: "http://example.com".to_string(),
|
||||||
|
mime_type: "application/json".to_string(),
|
||||||
|
title: Some("some title".to_string()),
|
||||||
|
size_in_bytes: Some(1),
|
||||||
|
duration_in_seconds: Some(1),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&attachment).unwrap(),
|
||||||
|
r#"{"url":"http://example.com","mime_type":"application/json","title":"some title","size_in_bytes":1,"duration_in_seconds":1}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_attachment() {
|
||||||
|
let json = r#"{"url":"http://example.com","mime_type":"application/json","title":"some title","size_in_bytes":1,"duration_in_seconds":1}"#;
|
||||||
|
let attachment: Attachment = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Attachment {
|
||||||
|
url: "http://example.com".to_string(),
|
||||||
|
mime_type: "application/json".to_string(),
|
||||||
|
title: Some("some title".to_string()),
|
||||||
|
size_in_bytes: Some(1),
|
||||||
|
duration_in_seconds: Some(1),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
attachment,
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_author() {
|
||||||
|
let author = Author {
|
||||||
|
name: Some("bob jones".to_string()),
|
||||||
|
url: Some("http://example.com".to_string()),
|
||||||
|
avatar: Some("http://img.com/blah".to_string()),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&author).unwrap(),
|
||||||
|
r#"{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_author() {
|
||||||
|
let json = r#"{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"}"#;
|
||||||
|
let author: Author = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Author {
|
||||||
|
name: Some("bob jones".to_string()),
|
||||||
|
url: Some("http://example.com".to_string()),
|
||||||
|
avatar: Some("http://img.com/blah".to_string()),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
author,
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_hub() {
|
||||||
|
let hub = Hub {
|
||||||
|
type_: "some-type".to_string(),
|
||||||
|
url: "http://example.com".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&hub).unwrap(),
|
||||||
|
r#"{"type":"some-type","url":"http://example.com"}"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_hub() {
|
||||||
|
let json = r#"{"type":"some-type","url":"http://example.com"}"#;
|
||||||
|
let hub: Hub = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Hub {
|
||||||
|
type_: "some-type".to_string(),
|
||||||
|
url: "http://example.com".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
hub,
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deser_podcast() {
|
||||||
|
let json = r#"{
|
||||||
|
"version": "https://jsonfeed.org/version/1",
|
||||||
|
"title": "Timetable",
|
||||||
|
"home_page_url": "http://timetable.manton.org/",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "http://timetable.manton.org/2017/04/episode-45-launch-week/",
|
||||||
|
"url": "http://timetable.manton.org/2017/04/episode-45-launch-week/",
|
||||||
|
"title": "Episode 45: Launch week",
|
||||||
|
"content_html": "I’m rolling out early access to Micro.blog this week. I talk about how the first 2 days have gone, mistakes with TestFlight, and what to do next.",
|
||||||
|
"date_published": "2017-04-26T01:09:45+00:00",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"url": "http://timetable.manton.org/podcast-download/139/episode-45-launch-week.mp3",
|
||||||
|
"mime_type": "audio/mpeg",
|
||||||
|
"size_in_bytes": 5236920
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
serde_json::from_str::<Feed>(&json).expect("Failed to deserialize podcast feed");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,493 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use feed::{Author, Attachment};
|
||||||
|
use builder::ItemBuilder;
|
||||||
|
|
||||||
|
use serde::ser::{Serialize, Serializer, SerializeStruct};
|
||||||
|
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};
|
||||||
|
|
||||||
|
/// Represents the `content_html` and `content_text` attributes of an item
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub enum Content {
|
||||||
|
Html(String),
|
||||||
|
Text(String),
|
||||||
|
Both(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an item in a feed
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Item {
|
||||||
|
pub id: String,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub external_url: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub content: Content,
|
||||||
|
pub summary: Option<String>,
|
||||||
|
pub image: Option<String>,
|
||||||
|
pub banner_image: Option<String>,
|
||||||
|
pub date_published: Option<String>, // todo DateTime objects?
|
||||||
|
pub date_modified: Option<String>,
|
||||||
|
pub author: Option<Author>,
|
||||||
|
pub tags: Option<Vec<String>>,
|
||||||
|
pub attachments: Option<Vec<Attachment>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item {
|
||||||
|
pub fn builder() -> ItemBuilder {
|
||||||
|
ItemBuilder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Item {
|
||||||
|
fn default() -> Item {
|
||||||
|
Item {
|
||||||
|
id: "".to_string(),
|
||||||
|
url: None,
|
||||||
|
external_url: None,
|
||||||
|
title: None,
|
||||||
|
content: Content::Text("".into()),
|
||||||
|
summary: None,
|
||||||
|
image: None,
|
||||||
|
banner_image: None,
|
||||||
|
date_published: None,
|
||||||
|
date_modified: None,
|
||||||
|
author: None,
|
||||||
|
tags: None,
|
||||||
|
attachments: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Item {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("Item", 14)?;
|
||||||
|
state.serialize_field("id", &self.id)?;
|
||||||
|
if self.url.is_some() {
|
||||||
|
state.serialize_field("url", &self.url)?;
|
||||||
|
}
|
||||||
|
if self.external_url.is_some() {
|
||||||
|
state.serialize_field("external_url", &self.external_url)?;
|
||||||
|
}
|
||||||
|
if self.title.is_some() {
|
||||||
|
state.serialize_field("title", &self.title)?;
|
||||||
|
}
|
||||||
|
match self.content {
|
||||||
|
Content::Html(ref s) => {
|
||||||
|
state.serialize_field("content_html", s)?;
|
||||||
|
state.serialize_field("content_text", &None::<Option<&str>>)?;
|
||||||
|
},
|
||||||
|
Content::Text(ref s) => {
|
||||||
|
state.serialize_field("content_html", &None::<Option<&str>>)?;
|
||||||
|
state.serialize_field("content_text", s)?;
|
||||||
|
},
|
||||||
|
Content::Both(ref s, ref t) => {
|
||||||
|
state.serialize_field("content_html", s)?;
|
||||||
|
state.serialize_field("content_text", t)?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if self.summary.is_some() {
|
||||||
|
state.serialize_field("summary", &self.summary)?;
|
||||||
|
}
|
||||||
|
if self.image.is_some() {
|
||||||
|
state.serialize_field("image", &self.image)?;
|
||||||
|
}
|
||||||
|
if self.banner_image.is_some() {
|
||||||
|
state.serialize_field("banner_image", &self.banner_image)?;
|
||||||
|
}
|
||||||
|
if self.date_published.is_some() {
|
||||||
|
state.serialize_field("date_published", &self.date_published)?;
|
||||||
|
}
|
||||||
|
if self.date_modified.is_some() {
|
||||||
|
state.serialize_field("date_modified", &self.date_modified)?;
|
||||||
|
}
|
||||||
|
if self.author.is_some() {
|
||||||
|
state.serialize_field("author", &self.author)?;
|
||||||
|
}
|
||||||
|
if self.tags.is_some() {
|
||||||
|
state.serialize_field("tags", &self.tags)?;
|
||||||
|
}
|
||||||
|
if self.attachments.is_some() {
|
||||||
|
state.serialize_field("attachments", &self.attachments)?;
|
||||||
|
}
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Item {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
enum Field {
|
||||||
|
Id,
|
||||||
|
Url,
|
||||||
|
ExternalUrl,
|
||||||
|
Title,
|
||||||
|
ContentHtml,
|
||||||
|
ContentText,
|
||||||
|
Summary,
|
||||||
|
Image,
|
||||||
|
BannerImage,
|
||||||
|
DatePublished,
|
||||||
|
DateModified,
|
||||||
|
Author,
|
||||||
|
Tags,
|
||||||
|
Attachments,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Field {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
struct FieldVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for FieldVisitor {
|
||||||
|
type Value = Field;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("non-expected field")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Field, E>
|
||||||
|
where E: de::Error
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"id" => Ok(Field::Id),
|
||||||
|
"url" => Ok(Field::Url),
|
||||||
|
"external_url" => Ok(Field::ExternalUrl),
|
||||||
|
"title" => Ok(Field::Title),
|
||||||
|
"content_html" => Ok(Field::ContentHtml),
|
||||||
|
"content_text" => Ok(Field::ContentText),
|
||||||
|
"summary" => Ok(Field::Summary),
|
||||||
|
"image" => Ok(Field::Image),
|
||||||
|
"banner_image" => Ok(Field::BannerImage),
|
||||||
|
"date_published" => Ok(Field::DatePublished),
|
||||||
|
"date_modified" => Ok(Field::DateModified),
|
||||||
|
"author" => Ok(Field::Author),
|
||||||
|
"tags" => Ok(Field::Tags),
|
||||||
|
"attachments" => Ok(Field::Attachments),
|
||||||
|
_ => Err(de::Error::unknown_field(value, FIELDS)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_identifier(FieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItemVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for ItemVisitor {
|
||||||
|
type Value = Item;
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("non-expected thing")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Item, V::Error>
|
||||||
|
where V: MapAccess<'de>
|
||||||
|
{
|
||||||
|
let mut id = None;
|
||||||
|
let mut url = None;
|
||||||
|
let mut external_url = None;
|
||||||
|
let mut title = None;
|
||||||
|
let mut content_html: Option<String> = None;
|
||||||
|
let mut content_text: Option<String> = None;
|
||||||
|
let mut summary = None;
|
||||||
|
let mut image = None;
|
||||||
|
let mut banner_image = None;
|
||||||
|
let mut date_published = None;
|
||||||
|
let mut date_modified = None;
|
||||||
|
let mut author = None;
|
||||||
|
let mut tags = None;
|
||||||
|
let mut attachments = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::Id => {
|
||||||
|
if id.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("id"));
|
||||||
|
}
|
||||||
|
id = Some(map.next_value()?);
|
||||||
|
},
|
||||||
|
Field::Url => {
|
||||||
|
if url.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("url"));
|
||||||
|
}
|
||||||
|
url = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::ExternalUrl => {
|
||||||
|
if external_url.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("external_url"));
|
||||||
|
}
|
||||||
|
external_url = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Title => {
|
||||||
|
if title.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("title"));
|
||||||
|
}
|
||||||
|
title = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::ContentHtml => {
|
||||||
|
if content_html.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("content_html"));
|
||||||
|
}
|
||||||
|
content_html = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::ContentText => {
|
||||||
|
if content_text.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("content_text"));
|
||||||
|
}
|
||||||
|
content_text = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Summary => {
|
||||||
|
if summary.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("summary"));
|
||||||
|
}
|
||||||
|
summary = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Image => {
|
||||||
|
if image.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("image"));
|
||||||
|
}
|
||||||
|
image = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::BannerImage => {
|
||||||
|
if banner_image.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("banner_image"));
|
||||||
|
}
|
||||||
|
banner_image = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::DatePublished => {
|
||||||
|
if date_published.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("date_published"));
|
||||||
|
}
|
||||||
|
date_published = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::DateModified => {
|
||||||
|
if date_modified.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("date_modified"));
|
||||||
|
}
|
||||||
|
date_modified = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Author => {
|
||||||
|
if author.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("author"));
|
||||||
|
}
|
||||||
|
author = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Tags => {
|
||||||
|
if tags.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("tags"));
|
||||||
|
}
|
||||||
|
tags = map.next_value()?;
|
||||||
|
},
|
||||||
|
Field::Attachments => {
|
||||||
|
if attachments.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("attachments"));
|
||||||
|
}
|
||||||
|
attachments = map.next_value()?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = id.ok_or_else(|| de::Error::missing_field("id"))?;
|
||||||
|
let content = match (content_html, content_text) {
|
||||||
|
(Some(s), Some(t)) => {
|
||||||
|
Content::Both(s.to_string(), t.to_string())
|
||||||
|
},
|
||||||
|
(Some(s), _) => {
|
||||||
|
Content::Html(s.to_string())
|
||||||
|
},
|
||||||
|
(_, Some(t)) => {
|
||||||
|
Content::Text(t.to_string())
|
||||||
|
},
|
||||||
|
_ => return Err(de::Error::missing_field("content_html or content_text")),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Item {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
external_url,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
summary,
|
||||||
|
image,
|
||||||
|
banner_image,
|
||||||
|
date_published,
|
||||||
|
date_modified,
|
||||||
|
author,
|
||||||
|
tags,
|
||||||
|
attachments,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIELDS: &'static [&'static str] = &[
|
||||||
|
"id",
|
||||||
|
"url",
|
||||||
|
"external_url",
|
||||||
|
"title",
|
||||||
|
"content",
|
||||||
|
"summary",
|
||||||
|
"image",
|
||||||
|
"banner_image",
|
||||||
|
"date_published",
|
||||||
|
"date_modified",
|
||||||
|
"author",
|
||||||
|
"tags",
|
||||||
|
"attachments",
|
||||||
|
];
|
||||||
|
deserializer.deserialize_struct("Item", FIELDS, ItemVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use feed::Author;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn serialize_item__content_html() {
|
||||||
|
let item = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Html("<p>content</p>".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&item).unwrap(),
|
||||||
|
r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":"<p>content</p>","content_text":null,"summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn serialize_item__content_text() {
|
||||||
|
let item = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Text("content".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&item).unwrap(),
|
||||||
|
r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":null,"content_text":"content","summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn serialize_item__content_both() {
|
||||||
|
let item = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Both("<p>content</p>".into(), "content".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_string(&item).unwrap(),
|
||||||
|
r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":"<p>content</p>","content_text":"content","summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn deserialize_item__content_html() {
|
||||||
|
let json = r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":"<p>content</p>","content_text":null,"summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#;
|
||||||
|
let item: Item = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Html("<p>content</p>".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(item, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn deserialize_item__content_text() {
|
||||||
|
let json = r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":null,"content_text":"content","summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#;
|
||||||
|
let item: Item = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Text("content".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(item, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn deserialize_item__content_both() {
|
||||||
|
let json = r#"{"id":"1","url":"http://example.com/feed.json","external_url":"http://example.com/feed.json","title":"feed title","content_html":"<p>content</p>","content_text":"content","summary":"feed summary","image":"http://img.com/blah","banner_image":"http://img.com/blah","date_published":"2017-01-01 10:00:00","date_modified":"2017-01-01 10:00:00","author":{"name":"bob jones","url":"http://example.com","avatar":"http://img.com/blah"},"tags":["json","feed"],"attachments":[]}"#;
|
||||||
|
let item: Item = serde_json::from_str(&json).unwrap();
|
||||||
|
let expected = Item {
|
||||||
|
id: "1".into(),
|
||||||
|
url: Some("http://example.com/feed.json".into()),
|
||||||
|
external_url: Some("http://example.com/feed.json".into()),
|
||||||
|
title: Some("feed title".into()),
|
||||||
|
content: Content::Both("<p>content</p>".into(), "content".into()),
|
||||||
|
summary: Some("feed summary".into()),
|
||||||
|
image: Some("http://img.com/blah".into()),
|
||||||
|
banner_image: Some("http://img.com/blah".into()),
|
||||||
|
date_published: Some("2017-01-01 10:00:00".into()),
|
||||||
|
date_modified: Some("2017-01-01 10:00:00".into()),
|
||||||
|
author: Some(Author::new().name("bob jones").url("http://example.com").avatar("http://img.com/blah")),
|
||||||
|
tags: Some(vec!["json".into(), "feed".into()]),
|
||||||
|
attachments: Some(vec![]),
|
||||||
|
};
|
||||||
|
assert_eq!(item, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
//! JSON Feed is a syndication format similar to ATOM and RSS, using JSON
|
||||||
|
//! instead of XML
|
||||||
|
//!
|
||||||
|
//! This crate can serialize and deserialize between JSON Feed strings
|
||||||
|
//! and Rust data structures. It also allows for programmatically building
|
||||||
|
//! a JSON Feed
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! extern crate jsonfeed;
|
||||||
|
//!
|
||||||
|
//! use jsonfeed::{Feed, Item};
|
||||||
|
//!
|
||||||
|
//! fn run() -> Result<(), jsonfeed::Error> {
|
||||||
|
//! let j = r#"{
|
||||||
|
//! "title": "my feed",
|
||||||
|
//! "version": "https://jsonfeed.org/version/1",
|
||||||
|
//! "items": []
|
||||||
|
//! }"#;
|
||||||
|
//! let feed = jsonfeed::from_str(j).unwrap();
|
||||||
|
//!
|
||||||
|
//! let new_feed = Feed::builder()
|
||||||
|
//! .title("some other feed")
|
||||||
|
//! .item(Item::builder()
|
||||||
|
//! .title("some item title")
|
||||||
|
//! .content_html("<p>Hello, World</p>")
|
||||||
|
//! .build()?)
|
||||||
|
//! .item(Item::builder()
|
||||||
|
//! .title("some other item title")
|
||||||
|
//! .content_text("Hello, World!")
|
||||||
|
//! .build()?)
|
||||||
|
//! .build();
|
||||||
|
//! println!("{}", jsonfeed::to_string(&new_feed).unwrap());
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! fn main() {
|
||||||
|
//! let _ = run();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use] extern crate error_chain;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
mod item;
|
||||||
|
mod feed;
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
pub use errors::*;
|
||||||
|
pub use item::*;
|
||||||
|
pub use feed::{Feed, Author, Attachment};
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// Attempts to convert a string slice to a Feed object
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate jsonfeed;
|
||||||
|
/// # use jsonfeed::Feed;
|
||||||
|
/// # use std::default::Default;
|
||||||
|
/// # fn main() {
|
||||||
|
/// let json = r#"{"version": "https://jsonfeed.org/version/1", "title": "", "items": []}"#;
|
||||||
|
/// let feed: Feed = jsonfeed::from_str(&json).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(feed, Feed::default());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn from_str(s: &str) -> Result<Feed> {
|
||||||
|
Ok(serde_json::from_str(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a Feed object from an IO stream of JSON
|
||||||
|
pub fn from_reader<R: ::std::io::Read>(r: R) -> Result<Feed> {
|
||||||
|
Ok(serde_json::from_reader(r)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a Feed object from bytes of JSON text
|
||||||
|
pub fn from_slice<'a>(v: &'a [u8]) -> Result<Feed> {
|
||||||
|
Ok(serde_json::from_slice(v)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a serde_json::Value type to a Feed object
|
||||||
|
pub fn from_value(value: serde_json::Value) -> Result<Feed> {
|
||||||
|
Ok(serde_json::from_value(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a Feed to a JSON Feed string
|
||||||
|
pub fn to_string(value: &Feed) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pretty-print a Feed to a JSON Feed string
|
||||||
|
pub fn to_string_pretty(value: &Feed) -> Result<String> {
|
||||||
|
Ok(serde_json::to_string_pretty(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a Feed to a serde_json::Value
|
||||||
|
pub fn to_value(value: Feed) -> Result<serde_json::Value> {
|
||||||
|
Ok(serde_json::to_value(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a Feed to a vector of bytes of JSON
|
||||||
|
pub fn to_vec(value: &Feed) -> Result<Vec<u8>> {
|
||||||
|
Ok(serde_json::to_vec(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a Feed to a vector of bytes of pretty-printed JSON
|
||||||
|
pub fn to_vec_pretty(value: &Feed) -> Result<Vec<u8>> {
|
||||||
|
Ok(serde_json::to_vec_pretty(value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a Feed to JSON and output to an IO stream
|
||||||
|
pub fn to_writer<W>(writer: W, value: &Feed) -> Result<()>
|
||||||
|
where W: Write
|
||||||
|
{
|
||||||
|
Ok(serde_json::to_writer(writer, value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a Feed to pretty-printed JSON and output to an IO stream
|
||||||
|
pub fn to_writer_pretty<W>(writer: W, value: &Feed) -> Result<()>
|
||||||
|
where W: Write
|
||||||
|
{
|
||||||
|
Ok(serde_json::to_writer_pretty(writer, value)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_str() {
|
||||||
|
let feed = r#"{"version": "https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let expected = Feed::default();
|
||||||
|
assert_eq!(
|
||||||
|
super::from_str(&feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn from_reader() {
|
||||||
|
let feed = r#"{"version": "https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let feed = feed.as_bytes();
|
||||||
|
let feed = Cursor::new(feed);
|
||||||
|
let expected = Feed::default();
|
||||||
|
assert_eq!(
|
||||||
|
super::from_reader(feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn from_slice() {
|
||||||
|
let feed = r#"{"version": "https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let feed = feed.as_bytes();
|
||||||
|
let expected = Feed::default();
|
||||||
|
assert_eq!(
|
||||||
|
super::from_slice(&feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn from_value() {
|
||||||
|
let feed = r#"{"version": "https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let feed: serde_json::Value = serde_json::from_str(&feed).unwrap();
|
||||||
|
let expected = Feed::default();
|
||||||
|
assert_eq!(
|
||||||
|
super::from_value(feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_string() {
|
||||||
|
let feed = Feed::default();
|
||||||
|
let expected = r#"{"version":"https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
assert_eq!(
|
||||||
|
super::to_string(&feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_string_pretty() {
|
||||||
|
let feed = Feed::default();
|
||||||
|
let expected = r#"{
|
||||||
|
"version": "https://jsonfeed.org/version/1",
|
||||||
|
"title": "",
|
||||||
|
"items": []
|
||||||
|
}"#;
|
||||||
|
assert_eq!(
|
||||||
|
super::to_string_pretty(&feed).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_value() {
|
||||||
|
let feed = r#"{"version":"https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let expected: serde_json::Value = serde_json::from_str(&feed).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
super::to_value(Feed::default()).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_vec() {
|
||||||
|
let feed = r#"{"version":"https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let expected = feed.as_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
super::to_vec(&Feed::default()).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_vec_pretty() {
|
||||||
|
let feed = r#"{
|
||||||
|
"version": "https://jsonfeed.org/version/1",
|
||||||
|
"title": "",
|
||||||
|
"items": []
|
||||||
|
}"#;
|
||||||
|
let expected = feed.as_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
super::to_vec_pretty(&Feed::default()).unwrap(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_writer() {
|
||||||
|
let feed = r#"{"version":"https://jsonfeed.org/version/1","title":"","items":[]}"#;
|
||||||
|
let feed = feed.as_bytes();
|
||||||
|
let mut writer = Cursor::new(Vec::with_capacity(feed.len()));
|
||||||
|
super::to_writer(&mut writer, &Feed::default()).expect("Could not write to writer");
|
||||||
|
let result = writer.into_inner();
|
||||||
|
assert_eq!(result, feed);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn to_writer_pretty() {
|
||||||
|
let feed = r#"{
|
||||||
|
"version": "https://jsonfeed.org/version/1",
|
||||||
|
"title": "",
|
||||||
|
"items": []
|
||||||
|
}"#;
|
||||||
|
let feed = feed.as_bytes();
|
||||||
|
let mut writer = Cursor::new(Vec::with_capacity(feed.len()));
|
||||||
|
super::to_writer_pretty(&mut writer, &Feed::default()).expect("Could not write to writer");
|
||||||
|
let result = writer.into_inner();
|
||||||
|
assert_eq!(result, feed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue