diff --git a/Cargo.lock b/Cargo.lock index e879a3a..a2053b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,12 +353,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "askama_web" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06f7c6ff1e5cb12c48c877eee90b58b5473705632ca74e66419d0d91e38fe402" +checksum = "1a91fdeb04bf77d96234780cdd58fc221eb10de7031e1782a22f40fc8ac1a313" dependencies = [ "actix-web", "askama", @@ -530,9 +530,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "basic-toml" @@ -626,9 +626,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.23" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "jobserver", "libc", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -1110,16 +1110,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "errno" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "etcetera" version = "0.8.0" @@ -1142,12 +1132,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "ff" version = "0.13.1" @@ -1590,11 +1574,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" dependencies = [ - "futures-util", "http 1.3.1", "hyper", "hyper-util", @@ -1603,7 +1586,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.11", + "webpki-roots", ] [[package]] @@ -1621,17 +1604,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1722,9 +1709,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -1738,9 +1725,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -1835,6 +1822,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1923,12 +1920,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - [[package]] name = "litemap" version = "0.8.0" @@ -1954,9 +1945,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -2026,14 +2017,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2140,6 +2131,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2310,9 +2307,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2320,9 +2317,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -2815,9 +2812,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64 0.22.1", "bytes", @@ -2841,7 +2838,6 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -2850,13 +2846,13 @@ dependencies = [ "tokio", "tokio-rustls", "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.11", - "windows-registry", + "webpki-roots", ] [[package]] @@ -3210,8 +3206,6 @@ dependencies = [ "rustical_xml", "serde", "sha2", - "strum", - "strum_macros", "thiserror 2.0.12", ] @@ -3254,8 +3248,6 @@ dependencies = [ "rustical_xml", "serde", "sha2", - "strum", - "strum_macros", "thiserror 2.0.12", "tokio", "tracing", @@ -3292,19 +3284,6 @@ dependencies = [ "xml_derive", ] -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustls" version = "0.23.27" @@ -3319,15 +3298,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -3351,9 +3321,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -3590,9 +3560,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3619,9 +3589,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3632,9 +3602,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64 0.22.1", "bytes", @@ -3668,9 +3638,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", @@ -3681,9 +3651,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", @@ -3700,16 +3670,15 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn", - "tempfile", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", @@ -3751,9 +3720,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", @@ -3790,9 +3759,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", @@ -3837,25 +3806,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" - -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3893,19 +3843,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4014,9 +3951,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -4178,6 +4115,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.3.1", + "http-body", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4385,12 +4340,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ "getrandom 0.3.3", + "js-sys", "rand 0.9.1", + "wasm-bindgen", ] [[package]] @@ -4542,15 +4499,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.0", -] - [[package]] name = "webpki-roots" version = "1.0.0" @@ -4603,15 +4551,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.1", + "windows-strings", ] [[package]] @@ -4642,40 +4590,20 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-result" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -4731,29 +4659,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4766,12 +4678,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4784,12 +4690,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4802,24 +4702,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4832,12 +4720,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4850,12 +4732,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4868,12 +4744,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4886,12 +4756,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.7.10" diff --git a/Cargo.toml b/Cargo.toml index 8e1edf9..e0aa637 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,7 @@ rustical_ical = { path = "./crates/ical/", features = ["actix"] } chrono-tz = "0.10" chrono-humanize = "0.2" rand = "0.8" +rrule = "0.14" argon2 = "0.5" rpassword = "7.3" password-hash = { version = "0.5" } diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index 43ffc5f..58157b1 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -1,5 +1,4 @@ -use super::ReportPropName; -use crate::Error; +use crate::{Error, calendar_object::resource::CalendarObjectPropWrapperName}; use actix_web::dev::{Path, ResourceDef}; use rustical_dav::xml::PropfindType; use rustical_ical::CalendarObject; @@ -11,7 +10,7 @@ use rustical_xml::XmlDeserialize; // pub(crate) struct CalendarMultigetRequest { #[xml(ty = "untagged")] - pub(crate) prop: PropfindType, + pub(crate) prop: PropfindType, #[xml(flatten)] #[xml(ns = "rustical_dav::namespace::NS_DAV")] pub(crate) href: Vec, diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index 46e6242..2f12183 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -1,5 +1,4 @@ -use super::ReportPropName; -use crate::Error; +use crate::{Error, calendar_object::resource::CalendarObjectPropWrapperName}; use rustical_dav::xml::PropfindType; use rustical_ical::{CalendarObject, UtcDateTime}; use rustical_store::{CalendarStore, calendar_store::CalendarQuery}; @@ -171,7 +170,7 @@ impl From<&FilterElement> for CalendarQuery { // pub struct CalendarQueryRequest { #[xml(ty = "untagged")] - pub prop: PropfindType, + pub prop: PropfindType, #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] pub(crate) filter: Option, #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] diff --git a/crates/caldav/src/calendar/methods/report/mod.rs b/crates/caldav/src/calendar/methods/report/mod.rs index 510b268..91a1e6b 100644 --- a/crates/caldav/src/calendar/methods/report/mod.rs +++ b/crates/caldav/src/calendar/methods/report/mod.rs @@ -1,6 +1,8 @@ use crate::{ CalDavPrincipalUri, Error, - calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource}, + calendar_object::resource::{ + CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource, + }, }; use actix_web::{ HttpRequest, Responder, @@ -12,11 +14,11 @@ use calendar_query::{CalendarQueryRequest, get_objects_calendar_query}; use rustical_dav::{ resource::{PrincipalUri, Resource}, xml::{ - MultistatusElement, PropElement, PropfindType, Propname, multistatus::ResponseElement, + MultistatusElement, PropfindType, multistatus::ResponseElement, sync_collection::SyncCollectionRequest, }, }; -use rustical_ical::{CalendarObject, UtcDateTime}; +use rustical_ical::CalendarObject; use rustical_store::{CalendarStore, auth::User}; use rustical_xml::{XmlDeserialize, XmlDocument}; use sync_collection::handle_sync_collection; @@ -26,34 +28,6 @@ mod calendar_multiget; mod calendar_query; mod sync_collection; -#[derive(XmlDeserialize, Clone, Debug, PartialEq)] -pub(crate) struct ExpandElement { - #[xml(ty = "attr")] - start: UtcDateTime, - #[xml(ty = "attr")] - end: UtcDateTime, -} - -#[derive(XmlDeserialize, Clone, Debug, PartialEq)] -pub struct CalendarData { - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - comp: Option<()>, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - expand: Option, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - limit_recurrence_set: Option<()>, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - limit_freebusy_set: Option<()>, -} - -#[derive(XmlDeserialize, Clone, Debug, PartialEq)] -pub enum ReportPropName { - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - CalendarData(CalendarData), - #[xml(other)] - Propname(Propname), -} - #[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)] pub(crate) enum ReportRequest { #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] @@ -61,31 +35,15 @@ pub(crate) enum ReportRequest { #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] CalendarQuery(CalendarQueryRequest), #[xml(ns = "rustical_dav::namespace::NS_DAV")] - SyncCollection(SyncCollectionRequest), + SyncCollection(SyncCollectionRequest), } impl ReportRequest { - fn props(&self) -> Vec<&str> { - let prop_element = match self { + fn props(&self) -> &PropfindType { + match &self { ReportRequest::CalendarMultiget(CalendarMultigetRequest { prop, .. }) => prop, ReportRequest::CalendarQuery(CalendarQueryRequest { prop, .. }) => prop, ReportRequest::SyncCollection(SyncCollectionRequest { prop, .. }) => prop, - }; - - match prop_element { - PropfindType::Allprop => { - vec!["allprop"] - } - PropfindType::Propname => { - vec!["propname"] - } - PropfindType::Prop(PropElement(prop_tags)) => prop_tags - .iter() - .map(|propname| match propname { - ReportPropName::Propname(propname) => propname.name.as_str(), - ReportPropName::CalendarData(_) => "calendar-data", - }) - .collect(), } } } @@ -97,7 +55,7 @@ fn objects_response( principal: &str, puri: &impl PrincipalUri, user: &User, - props: &[&str], + prop: &PropfindType, ) -> Result, Error> { let mut responses = Vec::new(); for object in objects { @@ -107,7 +65,7 @@ fn objects_response( object, principal: principal.to_owned(), } - .propfind(&path, props, puri, user)?, + .propfind_typed(&path, prop, puri, user)?, ); } @@ -156,7 +114,7 @@ pub async fn route_report_calendar( &principal, puri.as_ref(), &user, - &props, + props, )? } ReportRequest::CalendarMultiget(cal_multiget) => { @@ -175,13 +133,12 @@ pub async fn route_report_calendar( &principal, puri.as_ref(), &user, - &props, + props, )? } ReportRequest::SyncCollection(sync_collection) => { handle_sync_collection( sync_collection, - &props, req.path(), puri.as_ref(), &user, @@ -197,10 +154,11 @@ pub async fn route_report_calendar( #[cfg(test)] mod tests { use super::*; + use crate::calendar_object::resource::{CalendarData, CalendarObjectPropName, ExpandElement}; use calendar_query::{CompFilterElement, FilterElement, TimeRangeElement}; - use rustical_dav::xml::{PropElement, PropfindType, Propname}; + use rustical_dav::xml::PropElement; use rustical_ical::UtcDateTime; - use rustical_xml::ValueDeserialize; + use rustical_xml::{NamespaceOwned, ValueDeserialize}; #[test] fn test_xml_calendar_data() { @@ -222,13 +180,14 @@ mod tests { report_request, ReportRequest::CalendarMultiget(CalendarMultigetRequest { prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![ - ReportPropName::Propname(Propname{name: "getetag".to_owned(), ns: Some("DAV:".into())}), - ReportPropName::Propname(Propname{name: "displayname".to_owned(), ns: Some("DAV:".into())}), - ReportPropName::CalendarData(CalendarData { comp: None, expand: Some(ExpandElement { + CalendarObjectPropWrapperName::CalendarObject(CalendarObjectPropName::Getetag), + CalendarObjectPropWrapperName::CalendarObject(CalendarObjectPropName::CalendarData( + CalendarData { comp: None, expand: Some(ExpandElement { start: ::deserialize("20250426T220000Z").unwrap(), end: ::deserialize("20250503T220000Z").unwrap(), - }), limit_recurrence_set: None, limit_freebusy_set: None }) - ])), + }), limit_recurrence_set: None, limit_freebusy_set: None } + )), + ], vec![(Some(NamespaceOwned(Vec::from("DAV:"))), "displayname".to_string())])), href: vec![ "/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned() ] @@ -258,10 +217,12 @@ mod tests { assert_eq!( report_request, ReportRequest::CalendarQuery(CalendarQueryRequest { - prop: PropfindType::Prop(PropElement(vec![ReportPropName::Propname(Propname { - name: "getetag".to_owned(), - ns: Some("DAV:".into()) - })])), + prop: rustical_dav::xml::PropfindType::Prop(PropElement( + vec![CalendarObjectPropWrapperName::CalendarObject( + CalendarObjectPropName::Getetag + ),], + vec![] + )), filter: Some(FilterElement { comp_filter: CompFilterElement { is_not_defined: None, @@ -308,9 +269,8 @@ mod tests { report_request, ReportRequest::CalendarMultiget(CalendarMultigetRequest { prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![ - ReportPropName::Propname(Propname{name: "getetag".to_owned(), ns: Some("DAV:".into())}), - ReportPropName::Propname(Propname{name: "displayname".to_owned(), ns: Some("DAV:".into())}) - ])), + CalendarObjectPropWrapperName::CalendarObject(CalendarObjectPropName::Getetag), + ], vec![(Some(NamespaceOwned(Vec::from("DAV:"))), "displayname".to_string())])), href: vec![ "/caldav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned() ] diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index 7cecd54..38f0731 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -1,7 +1,8 @@ -use super::ReportPropName; use crate::{ Error, - calendar_object::resource::{CalendarObjectPropWrapper, CalendarObjectResource}, + calendar_object::resource::{ + CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource, + }, }; use actix_web::http::StatusCode; use rustical_dav::{ @@ -17,8 +18,7 @@ use rustical_store::{ }; pub async fn handle_sync_collection( - sync_collection: &SyncCollectionRequest, - props: &[&str], + sync_collection: &SyncCollectionRequest, path: &str, puri: &impl PrincipalUri, user: &User, @@ -39,7 +39,7 @@ pub async fn handle_sync_collection( object, principal: principal.to_owned(), } - .propfind(&path, props, puri, user)?, + .propfind_typed(&path, &sync_collection.prop, puri, user)?, ); } diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 11aef1f..ed83e68 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -19,12 +19,12 @@ use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; use rustical_ical::CalDateTime; use rustical_store::auth::User; use rustical_store::{Calendar, CalendarStore, SubscriptionStore}; -use rustical_xml::{EnumUnitVariants, EnumVariants}; +use rustical_xml::{EnumVariants, PropName}; use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::str::FromStr; use std::sync::Arc; -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarPropName")] pub enum CalendarProp { // WebDAV (RFC 2518) @@ -64,7 +64,7 @@ pub enum CalendarProp { MaxDateTime(String), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarPropWrapperName", untagged)] pub enum CalendarPropWrapper { Calendar(CalendarProp), diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 7bfc9d7..fdffd1f 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -9,9 +9,9 @@ use rustical_dav::{ resource::{PrincipalUri, Resource, ResourceService}, xml::Resourcetype, }; -use rustical_ical::CalendarObject; +use rustical_ical::{CalendarObject, UtcDateTime}; use rustical_store::{CalendarStore, auth::User}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use serde::Deserialize; use std::sync::Arc; @@ -25,7 +25,27 @@ impl CalendarObjectResourceService { } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct ExpandElement { + #[xml(ty = "attr")] + pub(crate) start: UtcDateTime, + #[xml(ty = "attr")] + pub(crate) end: UtcDateTime, +} + +#[derive(XmlDeserialize, Clone, Debug, PartialEq, Default, Eq, Hash)] +pub struct CalendarData { + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) comp: Option<()>, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) expand: Option, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) limit_recurrence_set: Option<()>, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) limit_freebusy_set: Option<()>, +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarObjectPropName")] pub enum CalendarObjectProp { // WebDAV (RFC 2518) @@ -36,10 +56,11 @@ pub enum CalendarObjectProp { // CalDAV (RFC 4791) #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + #[xml(prop = "CalendarData")] CalendarData(String), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarObjectPropWrapperName", untagged)] pub enum CalendarObjectPropWrapper { CalendarObject(CalendarObjectProp), @@ -73,8 +94,15 @@ impl Resource for CalendarObjectResource { CalendarObjectPropName::Getetag => { CalendarObjectProp::Getetag(self.object.get_etag()) } - CalendarObjectPropName::CalendarData => { - CalendarObjectProp::CalendarData(self.object.get_ics().to_owned()) + CalendarObjectPropName::CalendarData(CalendarData { expand, .. }) => { + CalendarObjectProp::CalendarData(if let Some(expand) = expand.as_ref() { + self.object.expand_recurrence( + Some(expand.start.to_utc()), + Some(expand.end.to_utc()), + )? + } else { + self.object.get_ics().to_owned() + }) } CalendarObjectPropName::Getcontenttype => { CalendarObjectProp::Getcontenttype("text/calendar;charset=utf-8") diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index 2487154..4ba4ba9 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -8,7 +8,7 @@ use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::{CalendarStore, SubscriptionStore}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use std::sync::Arc; #[derive(Clone)] @@ -17,7 +17,7 @@ pub struct CalendarSetResource { pub(crate) read_only: bool, } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] pub enum PrincipalPropWrapper { Common(CommonPropertiesProp), diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 7b836ae..41e94e7 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -9,7 +9,7 @@ use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::user::PrincipalType; use rustical_store::auth::{AuthenticationProvider, User}; use rustical_store::{CalendarStore, SubscriptionStore}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use std::sync::Arc; #[derive(Clone)] @@ -21,7 +21,7 @@ pub struct PrincipalResource { #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec); -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropName")] pub enum PrincipalProp { #[xml(ns = "rustical_dav::namespace::NS_DAV")] @@ -42,7 +42,7 @@ pub enum PrincipalProp { CalendarHomeSet(CalendarHomeSet), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] pub enum PrincipalPropWrapper { Principal(PrincipalProp), diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index b27a882..74af803 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -10,7 +10,7 @@ use rustical_dav::{ }; use rustical_ical::AddressObject; use rustical_store::{AddressbookStore, auth::User}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use serde::Deserialize; use std::sync::Arc; @@ -21,7 +21,7 @@ pub struct AddressObjectResourceService { addr_store: Arc, } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "AddressObjectPropName")] pub enum AddressObjectProp { // WebDAV (RFC 2518) @@ -35,7 +35,7 @@ pub enum AddressObjectProp { AddressData(String), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "AddressObjectPropWrapperName", untagged)] pub enum AddressObjectPropWrapper { AddressObject(AddressObjectProp), diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 390e279..666e2ae 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -1,6 +1,8 @@ use crate::{ Error, - address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, + address_object::resource::{ + AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource, + }, }; use actix_web::{ dev::{Path, ResourceDef}, @@ -19,7 +21,7 @@ use rustical_xml::XmlDeserialize; #[xml(ns = "rustical_dav::namespace::NS_DAV")] pub struct AddressbookMultigetRequest { #[xml(ns = "rustical_dav::namespace::NS_DAV", ty = "untagged")] - pub(crate) prop: PropfindType, + pub(crate) prop: PropfindType, #[xml(ns = "rustical_dav::namespace::NS_DAV", flatten)] pub(crate) href: Vec, } @@ -59,7 +61,7 @@ pub async fn get_objects_addressbook_multiget( pub async fn handle_addressbook_multiget( addr_multiget: &AddressbookMultigetRequest, - props: &[&str], + prop: &PropfindType, path: &str, puri: &impl PrincipalUri, user: &User, @@ -79,7 +81,7 @@ pub async fn handle_addressbook_multiget( object, principal: principal.to_owned(), } - .propfind(&path, props, puri, user)?, + .propfind_typed(&path, prop, puri, user)?, ); } diff --git a/crates/carddav/src/addressbook/methods/report/mod.rs b/crates/carddav/src/addressbook/methods/report/mod.rs index 0afbb09..e09b64d 100644 --- a/crates/carddav/src/addressbook/methods/report/mod.rs +++ b/crates/carddav/src/addressbook/methods/report/mod.rs @@ -1,10 +1,10 @@ -use crate::{CardDavPrincipalUri, Error}; +use crate::{CardDavPrincipalUri, Error, address_object::resource::AddressObjectPropWrapperName}; use actix_web::{ HttpRequest, Responder, web::{Data, Path}, }; use addressbook_multiget::{AddressbookMultigetRequest, handle_addressbook_multiget}; -use rustical_dav::xml::{PropElement, PropfindType, sync_collection::SyncCollectionRequest}; +use rustical_dav::xml::{PropfindType, sync_collection::SyncCollectionRequest}; use rustical_store::{AddressbookStore, auth::User}; use rustical_xml::{XmlDeserialize, XmlDocument}; use sync_collection::handle_sync_collection; @@ -18,27 +18,14 @@ pub(crate) enum ReportRequest { #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] AddressbookMultiget(AddressbookMultigetRequest), #[xml(ns = "rustical_dav::namespace::NS_DAV")] - SyncCollection(SyncCollectionRequest), + SyncCollection(SyncCollectionRequest), } impl ReportRequest { - fn props(&self) -> Vec<&str> { - let prop_element = match self { + fn props(&self) -> &PropfindType { + match self { ReportRequest::AddressbookMultiget(AddressbookMultigetRequest { prop, .. }) => prop, ReportRequest::SyncCollection(SyncCollectionRequest { prop, .. }) => prop, - }; - - match prop_element { - PropfindType::Allprop => { - vec!["allprop"] - } - PropfindType::Propname => { - vec!["propname"] - } - PropfindType::Prop(PropElement(prop_tags)) => prop_tags - .iter() - .map(|propname| propname.name.as_str()) - .collect(), } } } @@ -58,13 +45,12 @@ pub async fn route_report_addressbook( } let request = ReportRequest::parse_str(&body)?; - let props = request.props(); Ok(match &request { ReportRequest::AddressbookMultiget(addr_multiget) => { handle_addressbook_multiget( addr_multiget, - &props, + request.props(), req.path(), puri.as_ref(), &user, @@ -77,7 +63,6 @@ pub async fn route_report_addressbook( ReportRequest::SyncCollection(sync_collection) => { handle_sync_collection( sync_collection, - &props, req.path(), puri.as_ref(), &user, @@ -92,9 +77,9 @@ pub async fn route_report_addressbook( #[cfg(test)] mod tests { - use rustical_dav::xml::{PropElement, Propname, sync_collection::SyncLevel}; - use super::*; + use crate::address_object::resource::AddressObjectPropName; + use rustical_dav::xml::{PropElement, sync_collection::SyncLevel}; #[test] fn test_xml_sync_collection() { @@ -115,10 +100,12 @@ mod tests { ReportRequest::SyncCollection(SyncCollectionRequest { sync_token: "".to_owned(), sync_level: SyncLevel::One, - prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![Propname { - name: "getetag".to_owned(), - ns: Some("DAV:".into()) - }])), + prop: rustical_dav::xml::PropfindType::Prop(PropElement( + vec![AddressObjectPropWrapperName::AddressObject( + AddressObjectPropName::Getetag + )], + vec![] + )), limit: None }) ) @@ -141,9 +128,13 @@ mod tests { report_request, ReportRequest::AddressbookMultiget(AddressbookMultigetRequest { prop: rustical_dav::xml::PropfindType::Prop(PropElement(vec![ - Propname{name: "getetag".to_owned(), ns: Some("DAV:".into())}, - Propname{name: "address-data".to_owned(), ns: Some("urn:ietf:params:xml:ns:carddav".into())} - ])), + AddressObjectPropWrapperName::AddressObject( + AddressObjectPropName::Getetag + ), + AddressObjectPropWrapperName::AddressObject( + AddressObjectPropName::AddressData + ), + ], vec![])), href: vec![ "/carddav/user/user/6f787542-5256-401a-8db97003260da/ae7a998fdfd1d84a20391168962c62b".to_owned() ] diff --git a/crates/carddav/src/addressbook/methods/report/sync_collection.rs b/crates/carddav/src/addressbook/methods/report/sync_collection.rs index 6f51b4a..c2b3922 100644 --- a/crates/carddav/src/addressbook/methods/report/sync_collection.rs +++ b/crates/carddav/src/addressbook/methods/report/sync_collection.rs @@ -1,6 +1,8 @@ use crate::{ Error, - address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, + address_object::resource::{ + AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource, + }, }; use actix_web::http::StatusCode; use rustical_dav::{ @@ -16,8 +18,7 @@ use rustical_store::{ }; pub async fn handle_sync_collection( - sync_collection: &SyncCollectionRequest, - props: &[&str], + sync_collection: &SyncCollectionRequest, path: &str, puri: &impl PrincipalUri, user: &User, @@ -38,7 +39,7 @@ pub async fn handle_sync_collection( object, principal: principal.to_owned(), } - .propfind(&path, props, puri, user)?, + .propfind_typed(&path, &sync_collection.prop, puri, user)?, ); } diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index fc71727..f74bd33 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -17,7 +17,7 @@ use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; use rustical_store::auth::User; use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use std::str::FromStr; use std::sync::Arc; @@ -35,7 +35,7 @@ impl AddressbookResourceService } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "AddressbookPropName")] pub enum AddressbookProp { // WebDAV (RFC 2518) @@ -53,7 +53,7 @@ pub enum AddressbookProp { MaxResourceSize(i64), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "AddressbookPropWrapperName", untagged)] pub enum AddressbookPropWrapper { Addressbook(AddressbookProp), diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index 10dd64a..92364c0 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -8,7 +8,7 @@ use rustical_dav::resource::{PrincipalUri, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::{AuthenticationProvider, User}; use rustical_store::{AddressbookStore, SubscriptionStore}; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; use std::sync::Arc; pub struct PrincipalResourceService< @@ -53,7 +53,7 @@ pub struct PrincipalResource { #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] pub struct AddressbookHomeSet(#[xml(ty = "untagged", flatten)] Vec); -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropName")] pub enum PrincipalProp { #[xml(ns = "rustical_dav::namespace::NS_DAV")] @@ -71,7 +71,7 @@ pub enum PrincipalProp { PrincipalAddress(Option), } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] pub enum PrincipalPropWrapper { Principal(PrincipalProp), diff --git a/crates/dav/src/extensions/common.rs b/crates/dav/src/extensions/common.rs index 399b599..bcbfa0b 100644 --- a/crates/dav/src/extensions/common.rs +++ b/crates/dav/src/extensions/common.rs @@ -4,9 +4,9 @@ use crate::{ resource::{PrincipalUri, Resource}, xml::{HrefElement, Resourcetype}, }; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, PropName, EnumVariants)] #[xml(unit_variants_ident = "CommonPropertiesPropName")] pub enum CommonPropertiesProp { // WebDAV (RFC 2518) diff --git a/crates/dav/src/extensions/synctoken.rs b/crates/dav/src/extensions/synctoken.rs index 2a4531e..ca9e48e 100644 --- a/crates/dav/src/extensions/synctoken.rs +++ b/crates/dav/src/extensions/synctoken.rs @@ -1,6 +1,6 @@ -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, PropName, EnumVariants)] #[xml(unit_variants_ident = "SyncTokenExtensionPropName")] pub enum SyncTokenExtensionProp { // Collection Synchronization (RFC 6578) diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 434012a..89c5d5e 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -5,9 +5,9 @@ use crate::resource::PrincipalUri; use crate::resource::Resource; use crate::resource::ResourceService; use crate::xml::MultistatusElement; -use crate::xml::PropElement; use crate::xml::PropfindElement; use crate::xml::PropfindType; +use rustical_xml::PropName; use rustical_xml::XmlDocument; use tracing::instrument; @@ -58,37 +58,36 @@ pub(crate) async fn route_propfind( } // A request body is optional. If empty we MUST return all props - let propfind: PropfindElement = if !body.is_empty() { - PropfindElement::parse_str(&body).map_err(Error::XmlError)? - } else { - PropfindElement { - prop: PropfindType::Allprop, - } - }; - - // TODO: respect namespaces? - let props = match &propfind.prop { - PropfindType::Allprop => vec!["allprop"], - PropfindType::Propname => vec!["propname"], - PropfindType::Prop(PropElement(prop_tags)) => prop_tags - .iter() - .map(|propname| propname.name.as_str()) - .collect(), - }; + let propfind_self: PropfindElement<<::Prop as PropName>::Names> = + if !body.is_empty() { + PropfindElement::parse_str(&body).map_err(Error::XmlError)? + } else { + PropfindElement { + prop: PropfindType::Allprop, + } + }; + let propfind_member: PropfindElement<<::Prop as PropName>::Names> = + if !body.is_empty() { + PropfindElement::parse_str(&body).map_err(Error::XmlError)? + } else { + PropfindElement { + prop: PropfindType::Allprop, + } + }; let mut member_responses = Vec::new(); if depth != Depth::Zero { for (subpath, member) in resource_service.get_members(path_components).await? { - member_responses.push(member.propfind( + member_responses.push(member.propfind_typed( &format!("{}/{}", path.trim_end_matches('/'), subpath), - &props, + &propfind_member.prop, puri, &user, )?); } } - let response = resource.propfind(path, &props, puri, &user)?; + let response = resource.propfind_typed(path, &propfind_self.prop, puri, &user)?; Ok(MultistatusElement { responses: vec![response], diff --git a/crates/dav/src/resource/methods/proppatch.rs b/crates/dav/src/resource/methods/proppatch.rs index 161e75c..c158c15 100644 --- a/crates/dav/src/resource/methods/proppatch.rs +++ b/crates/dav/src/resource/methods/proppatch.rs @@ -7,7 +7,8 @@ use crate::xml::TagList; use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement}; use http::StatusCode; use quick_xml::name::Namespace; -use rustical_xml::EnumUnitVariants; +use rustical_xml::NamespaceOwned; +use rustical_xml::PropName; use rustical_xml::Unparsed; use rustical_xml::XmlDeserialize; use rustical_xml::XmlDocument; @@ -111,13 +112,15 @@ pub(crate) async fn route_proppatch( }) => { match property { SetPropertyPropWrapper::Valid(prop) => { - let propname: <::Prop as EnumUnitVariants>::UnitVariants = prop.clone().into(); + let propname: <::Prop as PropName>::Names = + prop.clone().into(); let (ns, propname): (Option, &str) = propname.into(); match resource.set_prop(prop) { - Ok(()) => props_ok.push((ns, propname.to_owned())), - Err(Error::PropReadOnly) => { - props_conflict.push((ns, propname.to_owned())) + Ok(()) => { + props_ok.push((ns.map(NamespaceOwned::from), propname.to_owned())) } + Err(Error::PropReadOnly) => props_conflict + .push((ns.map(NamespaceOwned::from), propname.to_owned())), Err(err) => return Err(err.into()), }; } @@ -128,7 +131,7 @@ pub(crate) async fn route_proppatch( .into_iter() .find_map(|(ns, tag)| { if tag == propname.as_str() { - Some((ns, tag.to_owned())) + Some((ns.map(NamespaceOwned::from), tag.to_owned())) } else { None } @@ -146,14 +149,12 @@ pub(crate) async fn route_proppatch( } Operation::Remove(remove_el) => { let propname = remove_el.prop.0.0; - match <::Prop as EnumUnitVariants>::UnitVariants::from_str( - &propname, - ) { + match <::Prop as PropName>::Names::from_str(&propname) { Ok(prop) => match resource.remove_prop(&prop) { Ok(()) => props_ok.push((None, propname)), Err(Error::PropReadOnly) => props_conflict.push({ let (ns, tag) = prop.into(); - (ns, tag.to_owned()) + (ns.map(NamespaceOwned::from), tag.to_owned()) }), Err(err) => return Err(err.into()), }, diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index 9648653..9e81e21 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -1,14 +1,14 @@ +use crate::Principal; use crate::privileges::UserPrivilegeSet; -use crate::xml::Resourcetype; use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper}; +use crate::xml::{PropElement, PropfindType, Resourcetype}; use crate::xml::{TagList, multistatus::ResponseElement}; -use crate::{Error, Principal}; use headers::{ETag, IfMatch, IfNoneMatch}; use http::StatusCode; use itertools::Itertools; use quick_xml::name::Namespace; pub use resource_service::ResourceService; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, NamespaceOwned, PropName, XmlDeserialize, XmlSerialize}; use std::collections::HashSet; use std::str::FromStr; @@ -26,7 +26,7 @@ pub trait ResourcePropName: FromStr {} impl ResourcePropName for T {} pub trait Resource: Clone + 'static { - type Prop: ResourceProp + PartialEq + Clone + EnumVariants + EnumUnitVariants; + type Prop: ResourceProp + PartialEq + Clone + EnumVariants + PropName; type Error: From; type Principal: Principal; @@ -40,17 +40,14 @@ pub trait Resource: Clone + 'static { &self, principal_uri: &impl PrincipalUri, principal: &Self::Principal, - prop: &::UnitVariants, + prop: &::Names, ) -> Result; fn set_prop(&mut self, _prop: Self::Prop) -> Result<(), crate::Error> { Err(crate::Error::PropReadOnly) } - fn remove_prop( - &mut self, - _prop: &::UnitVariants, - ) -> Result<(), crate::Error> { + fn remove_prop(&mut self, _prop: &::Names) -> Result<(), crate::Error> { Err(crate::Error::PropReadOnly) } @@ -91,62 +88,45 @@ pub trait Resource: Clone + 'static { principal: &Self::Principal, ) -> Result; - fn propfind( + fn propfind_typed( &self, path: &str, - props: &[&str], + prop: &PropfindType<::Names>, principal_uri: &impl PrincipalUri, principal: &Self::Principal, ) -> Result, Self::Error> { - let mut props: HashSet<&str> = props.iter().cloned().collect(); + // TODO: Support include element + let (props, invalid_props): (HashSet<::Names>, Vec<_>) = match prop + { + PropfindType::Propname => { + let props = Self::list_props() + .into_iter() + .map(|(ns, tag)| (ns.map(NamespaceOwned::from), tag.to_string())) + .collect_vec(); - if props.contains(&"propname") { - if props.len() != 1 { - // propname MUST be the only queried prop per spec - return Err( - Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(), - ); + return Ok(ResponseElement { + href: path.to_owned(), + propstat: vec![PropstatWrapper::TagList(PropstatElement { + prop: TagList::from(props), + status: StatusCode::OK, + })], + ..Default::default() + }); } + PropfindType::Allprop => ( + Self::list_props() + .iter() + .map(|(_ns, name)| ::Names::from_str(name).unwrap()) + .collect(), + vec![], + ), + PropfindType::Prop(PropElement(valid_tags, invalid_tags)) => ( + valid_tags.iter().cloned().collect(), + invalid_tags.to_owned(), + ), + }; - let props = Self::list_props() - .into_iter() - .map(|(ns, tag)| (ns.to_owned(), tag.to_string())) - .collect_vec(); - - return Ok(ResponseElement { - href: path.to_owned(), - propstat: vec![PropstatWrapper::TagList(PropstatElement { - prop: TagList::from(props), - status: StatusCode::OK, - })], - ..Default::default() - }); - } - - if props.contains(&"allprop") { - if props.len() != 1 { - // allprop MUST be the only queried prop per spec - return Err( - Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(), - ); - } - props = Self::list_props() - .into_iter() - .map(|(_ns, tag)| tag) - .collect(); - } - - let mut valid_props = vec![]; - let mut invalid_props = vec![]; - for prop in props { - if let Ok(valid_prop) = ::UnitVariants::from_str(prop) { - valid_props.push(valid_prop); - } else { - invalid_props.push(prop.to_string()) - } - } - - let prop_responses = valid_props + let prop_responses = props .into_iter() .map(|prop| self.get_prop(principal_uri, principal, &prop)) .collect::, Self::Error>>()?; @@ -158,11 +138,7 @@ pub trait Resource: Clone + 'static { if !invalid_props.is_empty() { propstats.push(PropstatWrapper::TagList(PropstatElement { status: StatusCode::NOT_FOUND, - prop: invalid_props - .into_iter() - .map(|tag| (None, tag)) - .collect_vec() - .into(), + prop: invalid_props.into(), })); } Ok(ResponseElement { diff --git a/crates/dav/src/xml/mod.rs b/crates/dav/src/xml/mod.rs index 6de656c..8f6d55d 100644 --- a/crates/dav/src/xml/mod.rs +++ b/crates/dav/src/xml/mod.rs @@ -4,7 +4,7 @@ mod resourcetype; pub mod tag_list; use derive_more::derive::From; pub use multistatus::MultistatusElement; -pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; +pub use propfind::{PropElement, PropfindElement, PropfindType}; pub use resourcetype::{Resourcetype, ResourcetypeInner}; use rustical_xml::{XmlDeserialize, XmlSerialize}; pub use tag_list::TagList; diff --git a/crates/dav/src/xml/propfind.rs b/crates/dav/src/xml/propfind.rs index f11356e..c8232c9 100644 --- a/crates/dav/src/xml/propfind.rs +++ b/crates/dav/src/xml/propfind.rs @@ -1,27 +1,85 @@ +use quick_xml::events::Event; +use quick_xml::name::ResolveResult; use rustical_xml::NamespaceOwned; +use rustical_xml::Unparsed; use rustical_xml::XmlDeserialize; +use rustical_xml::XmlError; use rustical_xml::XmlRootTag; #[derive(Debug, Clone, XmlDeserialize, XmlRootTag, PartialEq)] #[xml(root = b"propfind", ns = "crate::namespace::NS_DAV")] -pub struct PropfindElement { +pub struct PropfindElement { #[xml(ty = "untagged")] - pub prop: PropfindType, + pub prop: PropfindType, +} + +#[derive(Debug, Clone, PartialEq)] +// pub struct PropElement(#[xml(ty = "untagged", flatten)] pub Vec); +pub struct PropElement( + // valid + pub Vec, + // invalid + pub Vec<(Option, String)>, +); + +impl XmlDeserialize for PropElement { + fn deserialize( + reader: &mut quick_xml::NsReader, + start: &quick_xml::events::BytesStart, + empty: bool, + ) -> Result { + if empty { + return Ok(Self(vec![], vec![])); + } + let mut buf = Vec::new(); + let mut valid_props = vec![]; + let mut invalid_props = vec![]; + loop { + let event = reader.read_event_into(&mut buf)?; + match &event { + Event::End(e) if e.name() == start.name() => { + break; + } + Event::Eof => return Err(XmlError::Eof), + // start of a child element + Event::Start(start) | Event::Empty(start) => { + let empty = matches!(event, Event::Empty(_)); + let (ns, name) = reader.resolve_element(start.name()); + let ns = match ns { + ResolveResult::Bound(ns) => Some(NamespaceOwned::from(ns)), + ResolveResult::Unknown(_ns) => todo!("handle error"), + ResolveResult::Unbound => None, + }; + + match PN::deserialize(reader, start, empty) { + Ok(propname) => valid_props.push(propname), + Err(XmlError::InvalidVariant(_)) => { + invalid_props + .push((ns, String::from_utf8_lossy(name.as_ref()).to_string())); + // Consume content + Unparsed::deserialize(reader, start, empty)?; + } + Err(err) => return Err(err), + } + } + Event::Text(_) | Event::CData(_) => { + return Err(XmlError::UnsupportedEvent("Not expecting text here")); + } + Event::Decl(_) | Event::Comment(_) | Event::DocType(_) | Event::PI(_) => { /* ignore */ + } + Event::End(_end) => { + unreachable!( + "Unexpected closing tag for wrong element, should be handled by quick_xml" + ); + } + } + } + Ok(Self(valid_props, invalid_props)) + } } #[derive(Debug, Clone, XmlDeserialize, PartialEq)] -pub struct PropElement(#[xml(ty = "untagged", flatten)] pub Vec); - -#[derive(Debug, Clone, XmlDeserialize, PartialEq)] -pub struct Propname { - #[xml(ty = "namespace")] - pub ns: Option, - #[xml(ty = "tag_name")] - pub name: String, -} - -#[derive(Debug, Clone, XmlDeserialize, PartialEq)] -pub enum PropfindType { +pub enum PropfindType { #[xml(ns = "crate::namespace::NS_DAV")] Propname, #[xml(ns = "crate::namespace::NS_DAV")] diff --git a/crates/dav/src/xml/sync_collection.rs b/crates/dav/src/xml/sync_collection.rs index cdcf81f..2382523 100644 --- a/crates/dav/src/xml/sync_collection.rs +++ b/crates/dav/src/xml/sync_collection.rs @@ -1,6 +1,6 @@ use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize}; -use super::{PropfindType, Propname}; +use super::PropfindType; #[derive(Clone, Debug, PartialEq)] pub enum SyncLevel { @@ -37,7 +37,7 @@ impl ValueSerialize for SyncLevel { // // #[xml(ns = "crate::namespace::NS_DAV")] -pub struct SyncCollectionRequest { +pub struct SyncCollectionRequest { #[xml(ns = "crate::namespace::NS_DAV")] pub sync_token: String, #[xml(ns = "crate::namespace::NS_DAV")] diff --git a/crates/dav/src/xml/tag_list.rs b/crates/dav/src/xml/tag_list.rs index c3e7ae8..3df9d43 100644 --- a/crates/dav/src/xml/tag_list.rs +++ b/crates/dav/src/xml/tag_list.rs @@ -1,10 +1,13 @@ use derive_more::derive::From; -use quick_xml::name::Namespace; -use rustical_xml::XmlSerialize; +use quick_xml::{ + events::{BytesStart, Event}, + name::Namespace, +}; +use rustical_xml::{NamespaceOwned, XmlSerialize}; use std::collections::HashMap; #[derive(Clone, Debug, PartialEq, From)] -pub struct TagList(Vec<(Option>, String)>); +pub struct TagList(Vec<(Option, String)>); impl XmlSerialize for TagList { fn serialize( @@ -14,22 +17,10 @@ impl XmlSerialize for TagList { namespaces: &HashMap, writer: &mut quick_xml::Writer, ) -> std::io::Result<()> { - #[derive(Debug, XmlSerialize, PartialEq)] - struct Inner(#[xml(ty = "untagged", flatten)] Vec); - - #[derive(Debug, XmlSerialize, PartialEq)] - struct Tag( - #[xml(ty = "namespace")] Option>, - #[xml(ty = "tag_name")] String, - ); - - Inner( - self.0 - .iter() - .map(|(ns, tag)| Tag(ns.to_owned(), tag.to_owned())) - .collect(), - ) - .serialize(ns, tag, namespaces, writer) + for (_ns, tag) in &self.0 { + writer.write_event(Event::Empty(BytesStart::new(tag)))?; + } + Ok(()) } #[allow(refining_impl_trait)] diff --git a/crates/dav/tests/propfind.rs b/crates/dav/tests/propfind.rs deleted file mode 100644 index 8fc8d1c..0000000 --- a/crates/dav/tests/propfind.rs +++ /dev/null @@ -1,87 +0,0 @@ -use rustical_dav::xml::{PropElement, PropfindElement, PropfindType, Propname}; -use rustical_xml::de::XmlDocument; - -#[test] -fn propfind_allprop() { - let propfind = PropfindElement::parse_str( - r#" - - - - "#, - ) - .unwrap(); - assert_eq!( - propfind, - PropfindElement { - prop: PropfindType::Allprop - } - ); -} - -#[test] -fn propfind_propname() { - let propfind = PropfindElement::parse_str( - r#" - - - - "#, - ) - .unwrap(); - assert_eq!( - propfind, - PropfindElement { - prop: PropfindType::Propname - } - ); -} - -#[test] -fn propfind_prop() { - let propfind = PropfindElement::parse_str( - r#" - - - - - - - "#, - ) - .unwrap(); - assert_eq!( - propfind, - PropfindElement { - prop: PropfindType::Prop(PropElement(vec![ - Propname { - name: "displayname".to_owned(), - ns: Some("DAV:".to_owned().into()) - }, - Propname { - name: "color".to_owned(), - ns: Some("DAV:".to_owned().into()) - }, - ])) - } - ); -} - -/// Example taken from DAVx5 -#[test] -fn propfind_decl() { - let propfind = PropfindElement::parse_str( - r#" - - - - - - - - - - - "# - ).unwrap(); -} diff --git a/crates/dav_push/src/extension.rs b/crates/dav_push/src/extension.rs index 4ddd0fe..266675e 100644 --- a/crates/dav_push/src/extension.rs +++ b/crates/dav_push/src/extension.rs @@ -1,8 +1,8 @@ use crate::{ContentUpdate, PropertyUpdate, SupportedTriggers, Transports, Trigger}; use rustical_dav::header::Depth; -use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, PropName, EnumVariants)] #[xml(unit_variants_ident = "DavPushExtensionPropName")] pub enum DavPushExtensionProp { // WebDav Push diff --git a/crates/ical/Cargo.toml b/crates/ical/Cargo.toml index 68916cb..ce20bed 100644 --- a/crates/ical/Cargo.toml +++ b/crates/ical/Cargo.toml @@ -17,9 +17,7 @@ rustical_xml.workspace = true ical.workspace = true lazy_static.workspace = true regex.workspace = true -strum.workspace = true -strum_macros.workspace = true -rrule = "0.14" +rrule.workspace = true serde.workspace = true sha2.workspace = true actix-web = { workspace = true, optional = true } diff --git a/crates/ical/src/icalendar/event.rs b/crates/ical/src/icalendar/event.rs index 00e448f..ce819d7 100644 --- a/crates/ical/src/icalendar/event.rs +++ b/crates/ical/src/icalendar/event.rs @@ -1,6 +1,6 @@ use crate::Error; use crate::{CalDateTime, ComponentMut, parse_duration}; -use chrono::{DateTime, Duration}; +use chrono::{DateTime, Duration, Utc}; use ical::{ generator::IcalEvent, parser::{Component, ical::component::IcalTimeZone}, @@ -89,8 +89,18 @@ impl EventObject { Ok(Some(rrule_set)) } - pub fn expand_recurrence(&self) -> Result, Error> { - if let Some(rrule_set) = self.recurrence_ruleset()? { + pub fn expand_recurrence( + &self, + start: Option>, + end: Option>, + ) -> Result, Error> { + if let Some(mut rrule_set) = self.recurrence_ruleset()? { + if let Some(start) = start { + rrule_set = rrule_set.after(start.with_timezone(&rrule::Tz::UTC)); + } + if let Some(end) = end { + rrule_set = rrule_set.before(end.with_timezone(&rrule::Tz::UTC)); + } let mut events = vec![]; let dates = rrule_set.all(2048).dates; @@ -205,7 +215,7 @@ END:VEVENT\r\n", let event = event.event().unwrap(); let events: Vec = event - .expand_recurrence() + .expand_recurrence(None, None) .unwrap() .into_iter() .map(|event| Emitter::generate(&event)) diff --git a/crates/ical/src/icalendar/object.rs b/crates/ical/src/icalendar/object.rs index f8be8c3..9360561 100644 --- a/crates/ical/src/icalendar/object.rs +++ b/crates/ical/src/icalendar/object.rs @@ -1,6 +1,8 @@ use super::{EventObject, JournalObject, TodoObject}; use crate::CalDateTime; use crate::Error; +use chrono::DateTime; +use chrono::Utc; use ical::{ generator::{Emitter, IcalCalendar}, parser::{Component, ical::component::IcalTimeZone}, @@ -188,12 +190,16 @@ impl CalendarObject { } } - pub fn expand_recurrence(&self) -> Result { + pub fn expand_recurrence( + &self, + start: Option>, + end: Option>, + ) -> Result { // Only events can be expanded match &self.data { CalendarObjectComponent::Event(event) => { let mut cal = self.cal.clone(); - cal.events = event.expand_recurrence()?; + cal.events = event.expand_recurrence(start, end)?; Ok(cal.generate()) } _ => Ok(self.get_ics().to_string()), diff --git a/crates/ical/src/timestamp.rs b/crates/ical/src/timestamp.rs index d6fecd1..5bfc68b 100644 --- a/crates/ical/src/timestamp.rs +++ b/crates/ical/src/timestamp.rs @@ -38,7 +38,7 @@ pub enum CalDateTimeError { InvalidDurationFormat(String), } -#[derive(Debug, Clone, Deref, PartialEq)] +#[derive(Debug, Clone, Deref, PartialEq, Eq, Hash)] pub struct UtcDateTime(pub DateTime); impl ValueDeserialize for UtcDateTime { diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 7638d1e..b872f4c 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -28,10 +28,8 @@ rand.workspace = true uuid.workspace = true clap.workspace = true rustical_dav.workspace = true -strum.workspace = true -strum_macros.workspace = true rustical_ical.workspace = true -rrule = "0.14" +rrule.workspace = true [dev-dependencies] rstest = { workspace = true } diff --git a/crates/store_sqlite/src/calendar_store.rs b/crates/store_sqlite/src/calendar_store.rs index 04d89a7..e5d3e05 100644 --- a/crates/store_sqlite/src/calendar_store.rs +++ b/crates/store_sqlite/src/calendar_store.rs @@ -252,7 +252,7 @@ impl SqliteCalendarStore { .fetch_all(executor) .await.map_err(crate::Error::from)? .into_iter() - .map(|row| row.try_into().map_err(rustical_store::Error::from)) + .map(|row| row.try_into()) .collect() } @@ -286,7 +286,7 @@ impl SqliteCalendarStore { .await .map_err(crate::Error::from)? .into_iter() - .map(|row| row.try_into().map_err(rustical_store::Error::from)) + .map(|row| row.try_into()) .collect() } diff --git a/crates/xml/derive/src/attrs.rs b/crates/xml/derive/src/attrs.rs index f845ff9..0d4cc11 100644 --- a/crates/xml/derive/src/attrs.rs +++ b/crates/xml/derive/src/attrs.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use darling::{FromDeriveInput, FromField, FromMeta, FromVariant, util::Flag}; -use syn::LitByteStr; +use syn::{Ident, LitByteStr}; #[derive(Debug, Default, FromMeta, Clone)] pub struct TagAttrs { @@ -16,6 +16,8 @@ pub struct VariantAttrs { pub common: TagAttrs, pub other: Flag, pub skip_deserializing: Flag, + // This is actually only for the PropName trait + pub prop: Option, } #[derive(Default, FromDeriveInput, Clone)] diff --git a/crates/xml/derive/src/lib.rs b/crates/xml/derive/src/lib.rs index b217b53..ab506fa 100644 --- a/crates/xml/derive/src/lib.rs +++ b/crates/xml/derive/src/lib.rs @@ -74,13 +74,13 @@ pub fn derive_enum_variants(input: proc_macro::TokenStream) -> proc_macro::Token .into() } -#[proc_macro_derive(EnumUnitVariants, attributes(xml))] -pub fn derive_enum_unit_variants(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +#[proc_macro_derive(PropName, attributes(xml))] +pub fn derive_enum_prop_name(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); match &input.data { syn::Data::Struct(_) => panic!("Struct not supported"), - syn::Data::Enum(e) => Enum::parse(&input, e).impl_enum_unit_variants(), + syn::Data::Enum(e) => Enum::parse(&input, e).impl_enum_prop_name(), syn::Data::Union(_) => panic!("Union not supported"), } .into() diff --git a/crates/xml/derive/src/variant.rs b/crates/xml/derive/src/variant.rs index df3211a..5d72dc0 100644 --- a/crates/xml/derive/src/variant.rs +++ b/crates/xml/derive/src/variant.rs @@ -85,8 +85,8 @@ impl Variant { ) { (_, Fields::Named(_), _) => { panic!( - "struct variants are not supported, please use a tuple variant with a struct" - ) + "struct variants are not supported, please use a tuple variant with a struct" + ) } (false, Fields::Unnamed(FieldsUnnamed { unnamed, .. }), true) => { if unnamed.len() != 1 { @@ -165,16 +165,20 @@ impl Variant { } let field = unnamed.iter().next().unwrap(); quote! { - if let Ok(val) = <#field as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty) { - return Ok(Self::#ident(val)); + match <#field as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty) { + Ok(val) => { return Ok(Self::#ident(val)) } + Err(::rustical_xml::XmlError::InvalidVariant(..)) => {} + Err(err) => { return Err(err) } } } } Fields::Unit => { quote! { // Make sure that content is still consumed - if let Ok(_) = <() as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty) { - return Ok(Self::#ident); + match <() as ::rustical_xml::XmlDeserialize>::deserialize(reader, start, empty) { + Ok(val) => { return Ok(Self::#ident(val)) } + Err(::rustical_xml::XmlError::InvalidVariant(..)) => {} + Err(err) => { return Err(err) } } } } diff --git a/crates/xml/derive/src/xml_enum.rs b/crates/xml/derive/src/xml_enum.rs deleted file mode 100644 index 4faa235..0000000 --- a/crates/xml/derive/src/xml_enum.rs +++ /dev/null @@ -1,400 +0,0 @@ -use super::{attrs::EnumAttrs, Variant}; -use crate::attrs::VariantAttrs; -use core::panic; -use darling::{FromDeriveInput, FromVariant}; -use quote::quote; -use syn::{DataEnum, DeriveInput}; - -pub struct Enum { - attrs: EnumAttrs, - variants: Vec, - ident: syn::Ident, - generics: syn::Generics, -} - -impl Enum { - fn impl_de_untagged(&self) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let name = &self.ident; - - let variant_branches = self - .variants - .iter() - .filter_map(|variant| variant.untagged_branch()); - - quote! { - impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { - fn deserialize( - reader: &mut quick_xml::NsReader, - start: &quick_xml::events::BytesStart, - empty: bool - ) -> Result { - #(#variant_branches);* - - Err(rustical_xml::XmlError::InvalidVariant("could not match".to_owned())) - } - } - } - } - - fn impl_de_tagged(&self) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let name = &self.ident; - - let variant_branches = self.variants.iter().filter_map(Variant::tagged_branch); - - quote! { - impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { - fn deserialize( - reader: &mut quick_xml::NsReader, - start: &quick_xml::events::BytesStart, - empty: bool - ) -> Result { - let (_ns, name) = reader.resolve_element(start.name()); - - match name.as_ref() { - #(#variant_branches),* - name => { - // Handle invalid variant name - Err(rustical_xml::XmlError::InvalidVariant(String::from_utf8_lossy(name).to_string())) - } - } - } - } - } - } - - pub fn impl_de(&self) -> proc_macro2::TokenStream { - match self.attrs.untagged.is_present() { - true => self.impl_de_untagged(), - false => self.impl_de_tagged(), - } - } - - pub fn parse(input: &DeriveInput, data: &DataEnum) -> Self { - let attrs = EnumAttrs::from_derive_input(input).unwrap(); - - Self { - variants: data - .variants - .iter() - .map(|variant| Variant { - attrs: VariantAttrs::from_variant(variant).unwrap(), - variant: variant.to_owned(), - }) - .collect(), - attrs, - ident: input.ident.to_owned(), - generics: input.generics.to_owned(), - } - } - - pub fn impl_se(&self) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let ident = &self.ident; - let enum_untagged = self.attrs.untagged.is_present(); - let variant_serializers = self.variants.iter().map(Variant::se_branch); - - quote! { - impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { - fn serialize( - &self, - ns: Option<::quick_xml::name::Namespace>, - tag: Option<&[u8]>, - namespaces: &::std::collections::HashMap<::quick_xml::name::Namespace, &[u8]>, - writer: &mut ::quick_xml::Writer - ) -> ::std::io::Result<()> { - use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; - - let prefix = ns - .map(|ns| namespaces.get(&ns)) - .unwrap_or(None) - .map(|prefix| [*prefix, b":"].concat()); - let has_prefix = prefix.is_some(); - let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat()); - let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname)); - - const enum_untagged: bool = #enum_untagged; - - if let Some(qname) = &qname { - let mut bytes_start = BytesStart::from(qname.to_owned()); - if !has_prefix { - if let Some(ns) = &ns { - bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref())); - } - } - writer.write_event(Event::Start(bytes_start))?; - } - - #(#variant_serializers);* - - if let Some(qname) = &qname { - writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?; - } - Ok(()) - } - - fn attributes<'a>(&self) -> Option>> { - None - } - } - } - } - - pub fn impl_xml_document(&self) -> proc_macro2::TokenStream { - if self.attrs.untagged.is_present() { - panic!("XmlDocument only supported for untagged enums"); - } - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let ident = &self.ident; - - quote! { - impl #impl_generics ::rustical_xml::XmlDocument for #ident #type_generics #where_clause { - fn parse(mut reader: ::quick_xml::NsReader) -> Result - where - Self: ::rustical_xml::XmlDeserialize - { - use ::quick_xml::events::Event; - - let mut buf = Vec::new(); - loop { - let event = reader.read_event_into(&mut buf)?; - let empty = matches!(event, Event::Empty(_)); - - match event { - Event::Start(start) | Event::Empty(start) => { - return ::deserialize(&mut reader, &start, empty); - } - Event::Eof => return Err(::rustical_xml::XmlError::Eof), - Event::Text(bytes_text) => { - return Err(::rustical_xml::XmlError::UnsupportedEvent("Text")); - } - Event::CData(cdata) => { - return Err(::rustical_xml::XmlError::UnsupportedEvent("CDATA")); - } - Event::Decl(_) => { /* ignore this */ } - Event::Comment(_) => { /* ignore */ } - Event::DocType(_) => { /* ignore */ } - Event::PI(_) => { - return Err(::rustical_xml::XmlError::UnsupportedEvent("Processing instruction")); - } - Event::End(end) => { - unreachable!("Premature end of xml document, should be handled by quick_xml"); - } - }; - } - } - } - } - } - - pub fn impl_enum_variants(&self) -> proc_macro2::TokenStream { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let ident = &self.ident; - - if self.attrs.untagged.is_present() { - let untagged_variants = self.variants.iter().map(|variant| { - let ty = &variant.deserializer_type(); - quote! { #ty::variant_names() } - }); - quote! { - impl #impl_generics ::rustical_xml::EnumVariants for #ident #type_generics #where_clause { - const TAGGED_VARIANTS: &'static [(Option<::quick_xml::name::Namespace<'static>>, &'static str)] = &[]; - - fn variant_names() -> Vec<(Option<::quick_xml::name::Namespace<'static>>, &'static str)> { - [ - #(#untagged_variants),* - ].concat() - } - } - } - } else { - let tagged_variants = self.variants.iter().map(|variant| { - let ns = match &variant.attrs.common.ns { - Some(ns) => quote! { Some(#ns) }, - None => quote! { None }, - }; - let b_xml_name = variant.xml_name().value(); - let xml_name = String::from_utf8_lossy(&b_xml_name); - quote! {(#ns, #xml_name)} - }); - - quote! { - impl #impl_generics ::rustical_xml::EnumVariants for #ident #type_generics #where_clause { - const TAGGED_VARIANTS: &'static [(Option<::quick_xml::name::Namespace<'static>>, &'static str)] = &[ - #(#tagged_variants),* - ]; - - fn variant_names() -> Vec<(Option<::quick_xml::name::Namespace<'static>>, &'static str)> { - [Self::TAGGED_VARIANTS,].concat() - } - } - } - } - } - - pub fn impl_enum_unit_variants(&self) -> proc_macro2::TokenStream { - let unit_enum_ident = self - .attrs - .unit_variants_ident - .as_ref() - .expect("unit_variants_ident no set"); - let ident = &self.ident; - - if self.attrs.untagged.is_present() { - let variant_branches: Vec<_> = self - .variants - .iter() - .map(|variant| { - let variant_type = variant.deserializer_type(); - let variant_ident = &variant.variant.ident; - quote! { - #variant_ident (<#variant_type as ::rustical_xml::EnumUnitVariants>::UnitVariants) - } - }) - .collect(); - - let variant_idents: Vec<_> = self - .variants - .iter() - .map(|variant| &variant.variant.ident) - .collect(); - - let unit_to_output_branches = variant_idents.iter().map(|variant_ident| { - quote! { #unit_enum_ident::#variant_ident(val) => val.into() } - }); - - let str_to_unit_branches = self.variants.iter().map(|variant| { - let variant_type = variant.deserializer_type(); - let variant_ident = &variant.variant.ident; - quote! { - if let Ok(name) = <#variant_type as ::rustical_xml::EnumUnitVariants>::UnitVariants::from_str(val) { - return Ok(Self::#variant_ident(name)) - } - } - }); - - let from_enum_to_unit_branches = variant_idents.iter().map(|variant_ident| { - quote! { #ident::#variant_ident(val) => #unit_enum_ident::#variant_ident(val.into()) } - }); - - quote! { - #[derive(Clone, Debug, PartialEq)] - pub enum #unit_enum_ident { - #(#variant_branches),* - } - - impl ::rustical_xml::EnumUnitVariants for #ident { - type UnitVariants = #unit_enum_ident; - } - - impl From<#unit_enum_ident> for (Option<::quick_xml::name::Namespace<'static>>, &'static str) { - fn from(val: #unit_enum_ident) -> Self { - match val { - #(#unit_to_output_branches),* - } - } - } - - impl From<#ident> for #unit_enum_ident { - fn from(val: #ident) -> Self { - match val { - #(#from_enum_to_unit_branches),* - } - } - } - - impl ::std::str::FromStr for #unit_enum_ident { - type Err = ::rustical_xml::FromStrError; - - fn from_str(val: &str) -> Result { - #(#str_to_unit_branches);* - Err(::rustical_xml::FromStrError) - } - } - } - } else { - let tagged_variants: Vec<_> = self - .variants - .iter() - .filter(|variant| !variant.attrs.other.is_present()) - .collect(); - - let variant_outputs: Vec<_> = tagged_variants - .iter() - .map(|variant| { - let ns = match &variant.attrs.common.ns { - Some(ns) => quote! { Some(#ns) }, - None => quote! { None }, - }; - let b_xml_name = variant.xml_name().value(); - let xml_name = String::from_utf8_lossy(&b_xml_name); - quote! {(#ns, #xml_name)} - }) - .collect(); - - let variant_idents: Vec<_> = tagged_variants - .iter() - .map(|variant| &variant.variant.ident) - .collect(); - - let unit_to_output_branches = - variant_idents - .iter() - .zip(&variant_outputs) - .map(|(variant_ident, out)| { - quote! { #unit_enum_ident::#variant_ident => #out } - }); - - let from_enum_to_unit_branches = variant_idents.iter().map(|variant_ident| { - quote! { #ident::#variant_ident { .. } => #unit_enum_ident::#variant_ident } - }); - - let str_to_unit_branches = tagged_variants.iter().map(|variant| { - let variant_ident = &variant.variant.ident; - let b_xml_name = variant.xml_name().value(); - let xml_name = String::from_utf8_lossy(&b_xml_name); - quote! { #xml_name => Ok(#unit_enum_ident::#variant_ident) } - }); - - quote! { - #[derive(Clone, Debug, PartialEq)] - pub enum #unit_enum_ident { - #(#variant_idents),* - } - - - impl ::rustical_xml::EnumUnitVariants for #ident { - type UnitVariants = #unit_enum_ident; - } - - impl From<#unit_enum_ident> for (Option<::quick_xml::name::Namespace<'static>>, &'static str) { - fn from(val: #unit_enum_ident) -> Self { - match val { - #(#unit_to_output_branches),* - } - } - } - - impl From<#ident> for #unit_enum_ident { - fn from(val: #ident) -> Self { - match val { - #(#from_enum_to_unit_branches),* - } - } - } - - impl ::std::str::FromStr for #unit_enum_ident { - type Err = ::rustical_xml::FromStrError; - - fn from_str(val: &str) -> Result { - match val { - #(#str_to_unit_branches),*, - _ => Err(::rustical_xml::FromStrError) - } - } - } - } - } - } -} diff --git a/crates/xml/derive/src/xml_enum/impl_de.rs b/crates/xml/derive/src/xml_enum/impl_de.rs new file mode 100644 index 0000000..0ae64c9 --- /dev/null +++ b/crates/xml/derive/src/xml_enum/impl_de.rs @@ -0,0 +1,64 @@ +use crate::Variant; + +use super::Enum; +use quote::quote; + +impl Enum { + fn impl_de_untagged(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let name = &self.ident; + + let variant_branches = self + .variants + .iter() + .filter_map(|variant| variant.untagged_branch()); + + quote! { + impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { + fn deserialize( + reader: &mut quick_xml::NsReader, + start: &quick_xml::events::BytesStart, + empty: bool + ) -> Result { + #(#variant_branches);* + + Err(rustical_xml::XmlError::InvalidVariant("could not match".to_owned())) + } + } + } + } + + fn impl_de_tagged(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let name = &self.ident; + + let variant_branches = self.variants.iter().filter_map(Variant::tagged_branch); + + quote! { + impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { + fn deserialize( + reader: &mut quick_xml::NsReader, + start: &quick_xml::events::BytesStart, + empty: bool + ) -> Result { + let (_ns, name) = reader.resolve_element(start.name()); + + match name.as_ref() { + #(#variant_branches),* + name => { + // Handle invalid variant name + Err(rustical_xml::XmlError::InvalidVariant(String::from_utf8_lossy(name).to_string())) + } + } + } + } + } + } + + pub fn impl_de(&self) -> proc_macro2::TokenStream { + match self.attrs.untagged.is_present() { + true => self.impl_de_untagged(), + false => self.impl_de_tagged(), + } + } +} diff --git a/crates/xml/derive/src/xml_enum/impl_prop_name.rs b/crates/xml/derive/src/xml_enum/impl_prop_name.rs new file mode 100644 index 0000000..cee4b0c --- /dev/null +++ b/crates/xml/derive/src/xml_enum/impl_prop_name.rs @@ -0,0 +1,179 @@ +use super::Enum; +use quote::quote; + +impl Enum { + pub fn impl_enum_prop_name(&self) -> proc_macro2::TokenStream { + let unit_enum_ident = self + .attrs + .unit_variants_ident + .as_ref() + .expect("unit_variants_ident no set"); + let ident = &self.ident; + + if self.attrs.untagged.is_present() { + let variant_branches: Vec<_> = self + .variants + .iter() + .map(|variant| { + let variant_type = variant.deserializer_type(); + let variant_ident = &variant.variant.ident; + quote! { + #variant_ident (<#variant_type as ::rustical_xml::PropName>::Names) + } + }) + .collect(); + + let variant_idents: Vec<_> = self + .variants + .iter() + .map(|variant| &variant.variant.ident) + .collect(); + + let unit_to_output_branches = variant_idents.iter().map(|variant_ident| { + quote! { #unit_enum_ident::#variant_ident(val) => val.into() } + }); + + let str_to_unit_branches = self.variants.iter().map(|variant| { + let variant_type = variant.deserializer_type(); + let variant_ident = &variant.variant.ident; + quote! { + if let Ok(name) = <#variant_type as ::rustical_xml::PropName>::Names::from_str(val) { + return Ok(Self::#variant_ident(name)) + } + } + }); + + let from_enum_to_unit_branches = variant_idents.iter().map(|variant_ident| { + quote! { #ident::#variant_ident(val) => #unit_enum_ident::#variant_ident(val.into()) } + }); + + quote! { + #[derive(Clone, Debug, PartialEq, Hash, Eq, ::rustical_xml::XmlDeserialize)] + #[xml(untagged)] + pub enum #unit_enum_ident { + #(#variant_branches),* + } + + impl ::rustical_xml::PropName for #ident { + type Names = #unit_enum_ident; + } + + impl From<#unit_enum_ident> for (Option<::quick_xml::name::Namespace<'static>>, &'static str) { + fn from(val: #unit_enum_ident) -> Self { + match val { + #(#unit_to_output_branches),* + } + } + } + + impl From<#ident> for #unit_enum_ident { + fn from(val: #ident) -> Self { + match val { + #(#from_enum_to_unit_branches),* + } + } + } + + impl ::std::str::FromStr for #unit_enum_ident { + type Err = ::rustical_xml::FromStrError; + + fn from_str(val: &str) -> Result { + #(#str_to_unit_branches);* + Err(::rustical_xml::FromStrError) + } + } + } + } else { + let tagged_variants: Vec<_> = self + .variants + .iter() + .filter(|variant| !variant.attrs.other.is_present()) + .collect(); + + let prop_name_variants = tagged_variants.iter().map(|variant| { + let ident = &variant.variant.ident; + if let Some(proptype) = &variant.attrs.prop { + quote! {#ident(#proptype)} + } else { + quote! {#ident} + } + }); + + let unit_to_output_branches = tagged_variants.iter().map(|variant| { + let ns = match &variant.attrs.common.ns { + Some(ns) => quote! { Some(#ns) }, + None => quote! { None }, + }; + let b_xml_name = variant.xml_name().value(); + let xml_name = String::from_utf8_lossy(&b_xml_name); + let out = quote! {(#ns, #xml_name)}; + + let ident = &variant.variant.ident; + if variant.attrs.prop.is_some() { + quote! { #unit_enum_ident::#ident(..) => #out } + } else { + quote! { #unit_enum_ident::#ident => #out } + } + }); + + let from_enum_to_unit_branches = tagged_variants.iter().map(|variant| { + let variant_ident = &variant.variant.ident; + if variant.attrs.prop.is_some() { + quote! { #ident::#variant_ident { .. } => Self::#variant_ident (Default::default()) } + } else { + quote! { #ident::#variant_ident { .. } => Self::#variant_ident } + } + }); + + let str_to_unit_branches = tagged_variants.iter().map(|variant| { + let ident = &variant.variant.ident; + let b_xml_name = variant.xml_name().value(); + let xml_name = String::from_utf8_lossy(&b_xml_name); + if variant.attrs.prop.is_some() { + quote! { #xml_name => Ok(Self::#ident (Default::default())) } + } else { + quote! { #xml_name => Ok(Self::#ident) } + } + }); + + quote! { + #[derive(Clone, Debug, PartialEq, Eq, Hash, ::rustical_xml::XmlDeserialize)] + pub enum #unit_enum_ident { + #(#prop_name_variants),* + } + + + impl ::rustical_xml::PropName for #ident { + type Names = #unit_enum_ident; + } + + impl From<#unit_enum_ident> for (Option<::quick_xml::name::Namespace<'static>>, &'static str) { + fn from(val: #unit_enum_ident) -> Self { + match val { + #(#unit_to_output_branches),* + } + } + } + + impl From<#ident> for #unit_enum_ident { + fn from(val: #ident) -> Self { + match val { + #(#from_enum_to_unit_branches),* + } + } + } + + impl ::std::str::FromStr for #unit_enum_ident { + type Err = ::rustical_xml::FromStrError; + + fn from_str(val: &str) -> Result { + match val { + #(#str_to_unit_branches),*, + _ => Err(::rustical_xml::FromStrError) + } + } + } + } + } + } +} diff --git a/crates/xml/derive/src/xml_enum/impl_se.rs b/crates/xml/derive/src/xml_enum/impl_se.rs new file mode 100644 index 0000000..8f712da --- /dev/null +++ b/crates/xml/derive/src/xml_enum/impl_se.rs @@ -0,0 +1,59 @@ +use quote::quote; + +use crate::Variant; + +use super::Enum; + +impl Enum { + pub fn impl_se(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + let enum_untagged = self.attrs.untagged.is_present(); + let variant_serializers = self.variants.iter().map(Variant::se_branch); + + quote! { + impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { + fn serialize( + &self, + ns: Option<::quick_xml::name::Namespace>, + tag: Option<&[u8]>, + namespaces: &::std::collections::HashMap<::quick_xml::name::Namespace, &[u8]>, + writer: &mut ::quick_xml::Writer + ) -> ::std::io::Result<()> { + use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; + + let prefix = ns + .map(|ns| namespaces.get(&ns)) + .unwrap_or(None) + .map(|prefix| [*prefix, b":"].concat()); + let has_prefix = prefix.is_some(); + let tagname = tag.map(|tag| [&prefix.unwrap_or_default(), tag].concat()); + let qname = tagname.as_ref().map(|tagname| ::quick_xml::name::QName(tagname)); + + const enum_untagged: bool = #enum_untagged; + + if let Some(qname) = &qname { + let mut bytes_start = BytesStart::from(qname.to_owned()); + if !has_prefix { + if let Some(ns) = &ns { + bytes_start.push_attribute((b"xmlns".as_ref(), ns.as_ref())); + } + } + writer.write_event(Event::Start(bytes_start))?; + } + + #(#variant_serializers);* + + if let Some(qname) = &qname { + writer.write_event(Event::End(BytesEnd::from(qname.to_owned())))?; + } + Ok(()) + } + + fn attributes<'a>(&self) -> Option>> { + None + } + } + } + } +} diff --git a/crates/xml/derive/src/xml_enum/mod.rs b/crates/xml/derive/src/xml_enum/mod.rs new file mode 100644 index 0000000..4b55475 --- /dev/null +++ b/crates/xml/derive/src/xml_enum/mod.rs @@ -0,0 +1,129 @@ +use super::{Variant, attrs::EnumAttrs}; +use crate::attrs::VariantAttrs; +use core::panic; +use darling::{FromDeriveInput, FromVariant}; +use quote::quote; +use syn::{DataEnum, DeriveInput}; + +mod impl_de; +mod impl_prop_name; +mod impl_se; + +pub struct Enum { + attrs: EnumAttrs, + variants: Vec, + ident: syn::Ident, + generics: syn::Generics, +} + +impl Enum { + pub fn parse(input: &DeriveInput, data: &DataEnum) -> Self { + let attrs = EnumAttrs::from_derive_input(input).unwrap(); + + Self { + variants: data + .variants + .iter() + .map(|variant| Variant { + attrs: VariantAttrs::from_variant(variant).unwrap(), + variant: variant.to_owned(), + }) + .collect(), + attrs, + ident: input.ident.to_owned(), + generics: input.generics.to_owned(), + } + } + + pub fn impl_xml_document(&self) -> proc_macro2::TokenStream { + if self.attrs.untagged.is_present() { + panic!("XmlDocument only supported for untagged enums"); + } + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + + quote! { + impl #impl_generics ::rustical_xml::XmlDocument for #ident #type_generics #where_clause { + fn parse(mut reader: ::quick_xml::NsReader) -> Result + where + Self: ::rustical_xml::XmlDeserialize + { + use ::quick_xml::events::Event; + + let mut buf = Vec::new(); + loop { + let event = reader.read_event_into(&mut buf)?; + let empty = matches!(event, Event::Empty(_)); + + match event { + Event::Start(start) | Event::Empty(start) => { + return ::deserialize(&mut reader, &start, empty); + } + Event::Eof => return Err(::rustical_xml::XmlError::Eof), + Event::Text(bytes_text) => { + return Err(::rustical_xml::XmlError::UnsupportedEvent("Text")); + } + Event::CData(cdata) => { + return Err(::rustical_xml::XmlError::UnsupportedEvent("CDATA")); + } + Event::Decl(_) => { /* ignore this */ } + Event::Comment(_) => { /* ignore */ } + Event::DocType(_) => { /* ignore */ } + Event::PI(_) => { + return Err(::rustical_xml::XmlError::UnsupportedEvent("Processing instruction")); + } + Event::End(end) => { + unreachable!("Premature end of xml document, should be handled by quick_xml"); + } + }; + } + } + } + } + } + + pub fn impl_enum_variants(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + + if self.attrs.untagged.is_present() { + let untagged_variants = self.variants.iter().map(|variant| { + let ty = &variant.deserializer_type(); + quote! { #ty::variant_names() } + }); + quote! { + impl #impl_generics ::rustical_xml::EnumVariants for #ident #type_generics #where_clause { + const TAGGED_VARIANTS: &'static [(Option<::quick_xml::name::Namespace<'static>>, &'static str)] = &[]; + + fn variant_names() -> Vec<(Option<::quick_xml::name::Namespace<'static>>, &'static str)> { + [ + #(#untagged_variants),* + ].concat() + } + } + } + } else { + let tagged_variants = self.variants.iter().map(|variant| { + let ns = match &variant.attrs.common.ns { + Some(ns) => quote! { Some(#ns) }, + None => quote! { None }, + }; + let b_xml_name = variant.xml_name().value(); + let xml_name = String::from_utf8_lossy(&b_xml_name); + quote! {(#ns, #xml_name)} + }); + + quote! { + impl #impl_generics ::rustical_xml::EnumVariants for #ident #type_generics #where_clause { + const TAGGED_VARIANTS: &'static [(Option<::quick_xml::name::Namespace<'static>>, &'static str)] = &[ + #(#tagged_variants),* + ]; + + fn variant_names() -> Vec<(Option<::quick_xml::name::Namespace<'static>>, &'static str)> { + [Self::TAGGED_VARIANTS,].concat() + } + } + } + } + } +} diff --git a/crates/xml/src/lib.rs b/crates/xml/src/lib.rs index 8336a5a..725b5ca 100644 --- a/crates/xml/src/lib.rs +++ b/crates/xml/src/lib.rs @@ -1,5 +1,6 @@ use quick_xml::name::Namespace; use std::collections::HashMap; +use std::hash::Hash; use std::str::FromStr; pub mod de; @@ -17,8 +18,8 @@ pub use se::XmlSerialize; pub use se::XmlSerializeRoot; pub use unparsed::Unparsed; pub use value::{ParseValueError, ValueDeserialize, ValueSerialize}; -pub use xml_derive::EnumUnitVariants; pub use xml_derive::EnumVariants; +pub use xml_derive::PropName; pub use xml_derive::XmlRootTag; pub trait XmlRootTag { @@ -37,6 +38,12 @@ pub trait EnumVariants { fn variant_names() -> Vec<(Option>, &'static str)>; } -pub trait EnumUnitVariants: Sized { - type UnitVariants: Into<(Option>, &'static str)> + From + FromStr; +pub trait PropName: Sized { + type Names: Into<(Option>, &'static str)> + + Clone + + From + + FromStr + + Hash + + Eq + + XmlDeserialize; } diff --git a/crates/xml/tests/enum_variants.rs b/crates/xml/tests/enum_variants.rs index 5518bf0..1526108 100644 --- a/crates/xml/tests/enum_variants.rs +++ b/crates/xml/tests/enum_variants.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use quick_xml::name::Namespace; use rustical_xml::EnumVariants; -use xml_derive::EnumUnitVariants; +use xml_derive::PropName; pub const NS_DAV: Namespace = Namespace(b"DAV:"); pub const NS_DAVPUSH: Namespace = Namespace(b"https://bitfire.at/webdav-push"); @@ -12,13 +12,13 @@ pub const NS_ICAL: Namespace = Namespace(b"http://apple.com/ns/ical/"); pub const NS_CALENDARSERVER: Namespace = Namespace(b"http://calendarserver.org/ns/"); pub const NS_NEXTCLOUD: Namespace = Namespace(b"http://nextcloud.com/ns"); -#[derive(EnumVariants, EnumUnitVariants)] +#[derive(EnumVariants, PropName)] #[xml(unit_variants_ident = "ExtensionsPropName")] enum ExtensionProp { Hello, } -#[derive(EnumVariants, EnumUnitVariants)] +#[derive(EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarPropName")] enum CalendarProp { // WebDAV (RFC 2518) @@ -45,7 +45,7 @@ fn test_enum_tagged_variants() { ); } -#[derive(EnumVariants, EnumUnitVariants)] +#[derive(EnumVariants, PropName)] #[xml(untagged, unit_variants_ident = "UnionPropName")] enum UnionProp { Calendar(CalendarProp),