opds wip
This commit is contained in:
parent
47e8c74419
commit
faea154ff5
180
Cargo.lock
generated
180
Cargo.lock
generated
@ -135,6 +135,12 @@ version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -216,6 +222,7 @@ dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
@ -260,7 +267,7 @@ dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -337,6 +344,51 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.4.4"
|
||||
@ -502,13 +554,19 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@ -525,7 +583,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -534,7 +592,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http",
|
||||
@ -684,6 +742,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.22"
|
||||
@ -700,6 +764,17 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
@ -707,7 +782,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -840,6 +916,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@ -1068,6 +1150,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -1092,6 +1180,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
@ -1262,11 +1360,14 @@ dependencies = [
|
||||
"clap",
|
||||
"once_cell",
|
||||
"poem",
|
||||
"quick-xml",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"tera",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@ -1346,6 +1447,36 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@ -1418,6 +1549,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -1496,6 +1633,37 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
@ -1549,7 +1717,7 @@ version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
@ -12,9 +12,12 @@ poem = { version = "3.0.0", features = ["embed", "static-files"] }
|
||||
rust-embed = "8.3.0"
|
||||
serde = { workspace = true }
|
||||
serde_json = "1.0.116"
|
||||
serde_with = "3.8.1"
|
||||
tera = "1.19.1"
|
||||
thiserror = { workspace = true }
|
||||
time = { version = "0.3.36", features = ["macros", "serde", "formatting"] }
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
uuid = { version = "1.8.0", features = ["v4", "fast-rng"] }
|
||||
quick-xml = { version = "0.31.0", features = ["serialize"] }
|
||||
|
68
rusty-library/src/handlers/opds.rs
Normal file
68
rusty-library/src/handlers/opds.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use poem::{
|
||||
handler,
|
||||
web::{headers::ContentType, Data, WithContentType},
|
||||
IntoResponse,
|
||||
};
|
||||
use quick_xml::se::to_string;
|
||||
use time::macros::datetime;
|
||||
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
opds::{
|
||||
author::Author, content::Content, entry::Entry, feed::Feed, link::Link,
|
||||
media_type::MediaType, relation::Relation,
|
||||
},
|
||||
};
|
||||
|
||||
#[handler]
|
||||
pub async fn handler(state: Data<&Arc<AppState>>) -> Result<WithContentType<String>, poem::Error> {
|
||||
let author = Author {
|
||||
name: "Thallian".to_string(),
|
||||
uri: "https://code.vanwa.ch/shu/rusty-library".to_string(),
|
||||
email: None,
|
||||
};
|
||||
let home_link = Link {
|
||||
href: "/opds".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Start,
|
||||
title: Some("Home".to_string()),
|
||||
count: None,
|
||||
};
|
||||
let self_link = Link {
|
||||
href: "/opds".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let books_entry = Entry {
|
||||
title: "Books".to_string(),
|
||||
id: "rusty:books".to_string(),
|
||||
updated: datetime!(2024-05-06 19:14:54 UTC),
|
||||
content: Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "Index of all books".to_string(),
|
||||
},
|
||||
links: vec![Link {
|
||||
href: "/opds/books".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Subsection,
|
||||
title: None,
|
||||
count: None,
|
||||
}],
|
||||
};
|
||||
|
||||
let feed = Feed {
|
||||
title: "rusty-library".to_string(),
|
||||
id: "rusty:catalog".to_string(),
|
||||
updated: datetime!(2024-05-06 19:14:54 UTC),
|
||||
icon: "favicon.ico".to_string(),
|
||||
author,
|
||||
links: vec![home_link, self_link],
|
||||
entries: vec![books_entry],
|
||||
};
|
||||
let xml = feed.as_xml();
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
@ -24,11 +24,13 @@ mod handlers {
|
||||
pub mod cover;
|
||||
pub mod download;
|
||||
pub mod error;
|
||||
pub mod opds;
|
||||
pub mod paginated;
|
||||
pub mod recents;
|
||||
pub mod series;
|
||||
pub mod series_single;
|
||||
}
|
||||
mod opds;
|
||||
mod templates;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
@ -50,6 +52,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
|
||||
let app = Route::new()
|
||||
.at("/", get(handlers::recents::handler))
|
||||
.at("/opds", get(handlers::opds::handler))
|
||||
.at("/books", get(handlers::books::handler_init))
|
||||
.at("/books/:cursor/:sort_order", get(handlers::books::handler))
|
||||
.at("/series", get(handlers::series::handler_init))
|
||||
|
32
rusty-library/src/opds/author.rs
Normal file
32
rusty-library/src/opds/author.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename = "author")]
|
||||
pub struct Author {
|
||||
pub name: String,
|
||||
pub uri: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub email: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quick_xml::se::to_string;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init() -> Author {
|
||||
Author {
|
||||
name: "Rohal der Weise".to_string(),
|
||||
uri: "https://de.wiki-aventurica.de/wiki/Rohal_der_Weise".to_string(),
|
||||
email: Some("rohal@aventurien.de".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let author = init();
|
||||
let xml = to_string(&author).unwrap();
|
||||
assert_eq!(xml, "<author><name>Rohal der Weise</name><uri>https://de.wiki-aventurica.de/wiki/Rohal_der_Weise</uri><email>rohal@aventurien.de</email></author>");
|
||||
}
|
||||
}
|
12
rusty-library/src/opds/content.rs
Normal file
12
rusty-library/src/opds/content.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use super::media_type::MediaType;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename = "content")]
|
||||
pub struct Content {
|
||||
#[serde(rename = "@type")]
|
||||
pub media_type: MediaType,
|
||||
#[serde(rename = "$value")]
|
||||
pub content: String,
|
||||
}
|
64
rusty-library/src/opds/entry.rs
Normal file
64
rusty-library/src/opds/entry.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::{content::Content, link::Link};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename = "entry")]
|
||||
pub struct Entry {
|
||||
pub title: String,
|
||||
pub id: String,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub updated: OffsetDateTime,
|
||||
pub content: Content,
|
||||
#[serde(rename = "link")]
|
||||
pub links: Vec<Link>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quick_xml::se::to_string;
|
||||
use time::macros::datetime;
|
||||
|
||||
use crate::opds::{content::Content, media_type::MediaType, relation::Relation};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init() -> Entry {
|
||||
Entry {
|
||||
title: "Authors".to_string(),
|
||||
id: "rust:authors".to_string(),
|
||||
updated: datetime!(2024-05-06 19:14:54 UTC),
|
||||
content: Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "All authors".to_string(),
|
||||
},
|
||||
links: vec![
|
||||
Link {
|
||||
href: "/opds".to_string(),
|
||||
media_type: MediaType::Text,
|
||||
rel: Relation::Start,
|
||||
title: None,
|
||||
count: None,
|
||||
},
|
||||
Link {
|
||||
href: "/opds".to_string(),
|
||||
media_type: MediaType::Text,
|
||||
rel: Relation::Start,
|
||||
title: None,
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let entry = init();
|
||||
let xml = to_string(&entry).unwrap();
|
||||
assert_eq!(
|
||||
xml,
|
||||
r#"<entry><title>Authors</title><id>rust:authors</id><updated>2024-05-06T19:14:54Z</updated><content type="text">All authors</content><link href="/opds" type="text" rel="start"/><link href="/opds" type="text" rel="start"/></entry>"#
|
||||
);
|
||||
}
|
||||
}
|
59
rusty-library/src/opds/feed.rs
Normal file
59
rusty-library/src/opds/feed.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use quick_xml::{
|
||||
events::{BytesDecl, BytesStart, Event},
|
||||
se::to_string,
|
||||
Reader, Writer,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::{author::Author, entry::Entry, link::Link};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename = "feed")]
|
||||
pub struct Feed {
|
||||
pub title: String,
|
||||
pub id: String,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub updated: OffsetDateTime,
|
||||
pub icon: String,
|
||||
pub author: Author,
|
||||
#[serde(rename = "link")]
|
||||
pub links: Vec<Link>,
|
||||
#[serde(rename = "entry")]
|
||||
pub entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
impl Feed {
|
||||
pub fn as_xml(&self) -> String {
|
||||
let xml = to_string(&self).unwrap();
|
||||
let mut reader = Reader::from_str(&xml);
|
||||
reader.trim_text(true);
|
||||
|
||||
let declaration = BytesDecl::new("1.0", Some("UTF-8"), None);
|
||||
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
||||
writer.write_event(Event::Decl(declaration)).unwrap();
|
||||
|
||||
let mut feed_start = BytesStart::new("feed");
|
||||
feed_start.push_attribute(("xmlns", "http://www.w3.org/2005/Atom"));
|
||||
feed_start.push_attribute(("xmlns:xhtml", "http://www.w3.org/1999/xhtml"));
|
||||
feed_start.push_attribute(("xmlns:opds", "http://opds-spec.org/2010/catalog"));
|
||||
feed_start.push_attribute(("xmlns:opensearch", "http://a9.com/-/spec/opensearch/1.1/"));
|
||||
feed_start.push_attribute(("xmlns:dcterms", "http://purl.org/dc/terms/"));
|
||||
feed_start.push_attribute(("xmlns:thr", "http://purl.org/syndication/thread/1.0"));
|
||||
|
||||
loop {
|
||||
match reader.read_event() {
|
||||
Ok(Event::Start(e)) if e.name().as_ref() == b"feed" => writer
|
||||
.write_event(Event::Start(feed_start.clone()))
|
||||
.unwrap(),
|
||||
Ok(Event::Eof) => break,
|
||||
Ok(e) => writer.write_event(e).unwrap(),
|
||||
Err(e) => (),
|
||||
}
|
||||
}
|
||||
let result = writer.into_inner().into_inner();
|
||||
String::from_utf8(result).unwrap()
|
||||
}
|
||||
}
|
57
rusty-library/src/opds/link.rs
Normal file
57
rusty-library/src/opds/link.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{media_type::MediaType, relation::Relation};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename = "link")]
|
||||
pub struct Link {
|
||||
#[serde(rename = "@href")]
|
||||
pub href: String,
|
||||
#[serde(rename = "@type")]
|
||||
pub media_type: MediaType,
|
||||
#[serde(rename = "@rel")]
|
||||
pub rel: Relation,
|
||||
#[serde(rename = "@title")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(rename = "@thr:count")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub count: Option<u64>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quick_xml::se::to_string;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init() -> Link {
|
||||
Link {
|
||||
href: "/opds".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Start,
|
||||
title: Some("Home".to_string()),
|
||||
count: Some(10),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let mut link = init();
|
||||
let xml = to_string(&link).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
xml,
|
||||
r#"<link href="/opds" type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="start" title="Home" thr:count="10"/>"#
|
||||
);
|
||||
|
||||
link.title = None;
|
||||
link.count = None;
|
||||
let xml = to_string(&link).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
xml,
|
||||
r#"<link href="/opds" type="application/atom+xml;profile=opds-catalog;kind=navigation" rel="start"/>"#
|
||||
);
|
||||
}
|
||||
}
|
32
rusty-library/src/opds/media_type.rs
Normal file
32
rusty-library/src/opds/media_type.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use serde_with::SerializeDisplay;
|
||||
|
||||
#[derive(Debug, SerializeDisplay)]
|
||||
pub enum MediaType {
|
||||
Acquisition,
|
||||
Epub,
|
||||
Html,
|
||||
Jpeg,
|
||||
Navigation,
|
||||
Pdf,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MediaType::Acquisition => write!(
|
||||
f,
|
||||
"application/atom+xml;profile=opds-catalog;kind=aqcuisition"
|
||||
),
|
||||
MediaType::Epub => write!(f, "application/epub+zip"),
|
||||
MediaType::Html => write!(f, "text/html"),
|
||||
MediaType::Jpeg => write!(f, "image/jpeg"),
|
||||
MediaType::Navigation => write!(
|
||||
f,
|
||||
"application/atom+xml;profile=opds-catalog;kind=navigation"
|
||||
),
|
||||
MediaType::Pdf => write!(f, "application/pdf"),
|
||||
MediaType::Text => write!(f, "text"),
|
||||
}
|
||||
}
|
||||
}
|
7
rusty-library/src/opds/mod.rs
Normal file
7
rusty-library/src/opds/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod author;
|
||||
pub mod content;
|
||||
pub mod entry;
|
||||
pub mod feed;
|
||||
pub mod link;
|
||||
pub mod media_type;
|
||||
pub mod relation;
|
22
rusty-library/src/opds/relation.rs
Normal file
22
rusty-library/src/opds/relation.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use serde_with::SerializeDisplay;
|
||||
|
||||
#[derive(Debug, SerializeDisplay)]
|
||||
pub enum Relation {
|
||||
Image,
|
||||
Myself,
|
||||
Start,
|
||||
Subsection,
|
||||
Thumbnail,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Relation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Relation::Image => write!(f, "http://opds-spec.org/image"),
|
||||
Relation::Myself => write!(f, "self"),
|
||||
Relation::Start => write!(f, "start"),
|
||||
Relation::Subsection => write!(f, "subsection"),
|
||||
Relation::Thumbnail => write!(f, "http://opds-spec.org/image/thumbnail"),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user