From 649a7b8b1be97a5d43f48ceff0d3f396fadabbbc Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Wed, 22 May 2024 19:36:27 +0200 Subject: webdav propfind integration tests --- Cargo.lock | 228 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + aero-proto/src/imap/mailbox_view.rs | 2 +- aerogramme/Cargo.toml | 5 + aerogramme/tests/behavior.rs | 125 +++++++++++++++++--- aerogramme/tests/common/mod.rs | 26 +++- flake.nix | 2 + 7 files changed, 370 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6602ab..9f8ccb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,7 @@ dependencies = [ name = "aerogramme" version = "0.3.0" dependencies = [ + "aero-dav", "aero-proto", "aero-user", "anyhow", @@ -160,6 +161,8 @@ dependencies = [ "futures", "log", "nix", + "quick-xml", + "reqwest", "rpassword", "tokio", "tracing", @@ -952,6 +955,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 = "base64-simd" version = "0.8.0" @@ -1515,6 +1524,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1933,6 +1957,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -2103,6 +2143,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "iso8601" version = "0.6.1" @@ -2297,6 +2343,12 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2324,6 +2376,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.27.1" @@ -2435,12 +2505,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -2724,6 +2832,49 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.2", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite 0.2.13", + "rustls-pemfile 2.1.1", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -3075,6 +3226,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3299,6 +3462,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -3311,12 +3480,45 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.0.1", + "rustix 0.38.31", + "windows-sys 0.52.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -3435,6 +3637,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -3678,6 +3890,12 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -3979,6 +4197,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 68b1eae..0ee7889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ http-body-util = "0.1.1" hyper = "1.2" hyper-rustls = { version = "0.26", features = ["http2"] } hyper-util = { version = "0.1", features = ["full"] } +reqwest = { version = "0.12", features = [ "blocking" ]} # for testing purposes only # serialization, compression & parsing serde = "1.0.137" diff --git a/aero-proto/src/imap/mailbox_view.rs b/aero-proto/src/imap/mailbox_view.rs index de81556..0b808aa 100644 --- a/aero-proto/src/imap/mailbox_view.rs +++ b/aero-proto/src/imap/mailbox_view.rs @@ -638,10 +638,10 @@ mod tests { use imap_codec::ResponseCodec; use std::fs; - use aero_user::cryptoblob; use aero_collections::mail::mailbox::MailMeta; use aero_collections::mail::query::QueryResult; use aero_collections::unique_ident; + use aero_user::cryptoblob; use crate::imap::index::MailIndex; use crate::imap::mime_view; diff --git a/aerogramme/Cargo.toml b/aerogramme/Cargo.toml index ab62e44..77f3584 100644 --- a/aerogramme/Cargo.toml +++ b/aerogramme/Cargo.toml @@ -21,6 +21,11 @@ tracing.workspace = true tracing-subscriber.workspace = true rpassword.workspace = true +[dev-dependencies] +reqwest.workspace = true +aero-dav.workspace = true +quick-xml.workspace = true + [[test]] name = "behavior" path = "tests/behavior.rs" diff --git a/aerogramme/tests/behavior.rs b/aerogramme/tests/behavior.rs index 13baf0e..1786500 100644 --- a/aerogramme/tests/behavior.rs +++ b/aerogramme/tests/behavior.rs @@ -5,21 +5,25 @@ use crate::common::constants::*; use crate::common::fragments::*; fn main() { - rfc3501_imap4rev1_base(); + // IMAP + /*rfc3501_imap4rev1_base(); rfc6851_imapext_move(); rfc4551_imapext_condstore(); rfc2177_imapext_idle(); - rfc5161_imapext_enable(); // 1 - rfc3691_imapext_unselect(); // 2 - rfc7888_imapext_literal(); // 3 - rfc4315_imapext_uidplus(); // 4 - rfc5819_imapext_liststatus(); // 5 + rfc5161_imapext_enable(); + rfc3691_imapext_unselect(); + rfc7888_imapext_literal(); + rfc4315_imapext_uidplus(); + rfc5819_imapext_liststatus();*/ + + // WebDAV + rfc4918_webdav_core(); println!("✅ SUCCESS 🌟🚀🥳🙏🥹"); } fn rfc3501_imap4rev1_base() { println!("🧪 rfc3501_imap4rev1_base"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::None).context("check server capabilities")?; login(imap_socket, Account::Alice).context("login test")?; @@ -69,7 +73,7 @@ fn rfc3501_imap4rev1_base() { fn rfc3691_imapext_unselect() { println!("🧪 rfc3691_imapext_unselect"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { connect(imap_socket).context("server says hello")?; lmtp_handshake(lmtp_socket).context("handshake lmtp done")?; @@ -118,7 +122,7 @@ fn rfc3691_imapext_unselect() { fn rfc5161_imapext_enable() { println!("🧪 rfc5161_imapext_enable"); - common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| { connect(imap_socket).context("server says hello")?; login(imap_socket, Account::Alice).context("login test")?; enable(imap_socket, Enable::Utf8Accept, Some(Enable::Utf8Accept))?; @@ -132,7 +136,7 @@ fn rfc5161_imapext_enable() { fn rfc6851_imapext_move() { println!("🧪 rfc6851_imapext_move"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::Move).context("check server capabilities")?; @@ -174,7 +178,7 @@ fn rfc6851_imapext_move() { fn rfc7888_imapext_literal() { println!("🧪 rfc7888_imapext_literal"); - common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| { connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::LiteralPlus).context("check server capabilities")?; @@ -187,7 +191,7 @@ fn rfc7888_imapext_literal() { fn rfc4551_imapext_condstore() { println!("🧪 rfc4551_imapext_condstore"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { // Setup the test connect(imap_socket).context("server says hello")?; @@ -245,7 +249,7 @@ fn rfc4551_imapext_condstore() { fn rfc2177_imapext_idle() { println!("🧪 rfc2177_imapext_idle"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { // Test setup, check capability connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::Idle).context("check server capabilities")?; @@ -266,7 +270,7 @@ fn rfc2177_imapext_idle() { fn rfc4315_imapext_uidplus() { println!("🧪 rfc4315_imapext_uidplus"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { // Test setup, check capability, insert 2 emails connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::UidPlus).context("check server capabilities")?; @@ -320,7 +324,7 @@ fn rfc4315_imapext_uidplus() { /// ``` fn rfc5819_imapext_liststatus() { println!("🧪 rfc5819_imapext_liststatus"); - common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| { + common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| { // Test setup, check capability, add 2 emails, read 1 connect(imap_socket).context("server says hello")?; capability(imap_socket, Extension::ListStatus).context("check server capabilities")?; @@ -355,3 +359,94 @@ fn rfc5819_imapext_liststatus() { }) .expect("test fully run"); } + +use aero_dav::caltypes as cal; +use aero_dav::realization::All; +use aero_dav::types as dav; + +use crate::common::dav_deserialize; + +fn rfc4918_webdav_core() { + println!("🧪 rfc4918_webdav_core"); + common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| { + // --- PROPFIND --- + // empty request body (assume "allprop") + let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").send()?.text()?; + let multistatus = dav_deserialize::>(&body); + let root_propstats = multistatus.responses.iter() + .find_map(|v| match &v.status_or_propstat { + dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x), + _ => None, + }) + .expect("propstats for root must exist"); + + let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200"); + let display_name = root_success.prop.0.iter() + .find_map(|v| match v { dav::AnyProperty::Value(dav::Property::DisplayName(x)) => Some(x), _ => None } ) + .expect("root has a display name"); + let content_type = root_success.prop.0.iter() + .find_map(|v| match v { dav::AnyProperty::Value(dav::Property::GetContentType(x)) => Some(x), _ => None } ) + .expect("root has a content type"); + let resource_type = root_success.prop.0.iter() + .find_map(|v| match v { dav::AnyProperty::Value(dav::Property::ResourceType(x)) => Some(x), _ => None } ) + .expect("root has a resource type"); + + assert_eq!(display_name, "DAV Root"); + assert_eq!(content_type, "httpd/unix-directory"); + assert_eq!(resource_type, &[ dav::ResourceType::Collection ]); + + // propname + let propfind_req = r#""#; + let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?; + let multistatus = dav_deserialize::>(&body); + let root_propstats = multistatus.responses.iter() + .find_map(|v| match &v.status_or_propstat { + dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x), + _ => None, + }) + .expect("propstats for root must exist"); + let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200"); + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::DisplayName))).is_some()); + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::ResourceType))).is_some()); + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentType))).is_some()); + + // list of properties + let propfind_req = r#""#; + let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?; + let multistatus = dav_deserialize::>(&body); + let root_propstats = multistatus.responses.iter() + .find_map(|v| match &v.status_or_propstat { + dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x), + _ => None, + }) + .expect("propstats for root must exist"); + + let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200"); + let root_not_found = root_propstats.iter().find(|p| p.status.0.as_u16() == 404).expect("some propstats for root must be not found"); + + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::DisplayName(x)) if x == "DAV Root")).is_some()); + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::ResourceType(_)))).is_none()); + assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::GetContentType(_)))).is_none()); + assert!(root_not_found.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentLength))).is_some()); + + // depth 1 + + // check tree (calendar, Personal) + + // --- PUT --- + + // --- GET --- + + // --- DELETE --- + + + Ok(()) + }) + .expect("test fully run"); +} + +// @TODO ACL + +// @TODO CALDAV + +// @TODO SYNC diff --git a/aerogramme/tests/common/mod.rs b/aerogramme/tests/common/mod.rs index cbe0271..12f2764 100644 --- a/aerogramme/tests/common/mod.rs +++ b/aerogramme/tests/common/mod.rs @@ -8,10 +8,13 @@ use std::net::{Shutdown, TcpStream}; use std::process::Command; use std::thread; +use reqwest::blocking::Client; +use reqwest::header; + use constants::SMALL_DELAY; pub fn aerogramme_provider_daemon_dev( - mut fx: impl FnMut(&mut TcpStream, &mut TcpStream) -> Result<()>, + mut fx: impl FnMut(&mut TcpStream, &mut TcpStream, &mut Client) -> Result<()>, ) -> Result<()> { // Check port is not used (= free) before starting the test let mut max_retry = 20; @@ -53,8 +56,15 @@ pub fn aerogramme_provider_daemon_dev( let mut lmtp_socket = TcpStream::connect("[::1]:1025").context("lmtp socket must be connected")?; - println!("-- ready to test imap features --"); - let result = fx(&mut imap_socket, &mut lmtp_socket); + let mut headers = header::HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_static("Basic YWxpY2U6aHVudGVyMg=="), + ); + let mut http_client = Client::builder().default_headers(headers).build()?; + + println!("-- ready to test features --"); + let result = fx(&mut imap_socket, &mut lmtp_socket, &mut http_client); println!("-- test teardown --"); imap_socket @@ -97,3 +107,13 @@ pub fn read_first_u32(inp: &str) -> Result { .collect::() .parse::()?) } + +use aero_dav::xml::{Node, Reader}; +pub fn dav_deserialize>(src: &str) -> T { + futures::executor::block_on(async { + let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())) + .await + .expect("build reader"); + rdr.find().await.expect("parse XML") + }) +} diff --git a/flake.nix b/flake.nix index c6ae4ce..8dcd326 100644 --- a/flake.nix +++ b/flake.nix @@ -185,6 +185,8 @@ # Shell shell = gpkgs.mkShell { buildInputs = [ + gpkgs.openssl + gpkgs.pkg-config cargo2nix.packages.x86_64-linux.default fenix.packages.x86_64-linux.complete.toolchain #fenix.packages.x86_64-linux.rust-analyzer -- cgit v1.2.3