Compare commits

...

8 Commits

Author SHA1 Message Date
Lennart K
ee1faa4c20 version 0.4.8 2025-07-01 14:09:58 +02:00
Lennart K
1e999ca0cc feat(frontend): Add bodged field to create group collections 2025-07-01 14:09:32 +02:00
Lennart K
f27245f996 fix(store_sqlite): Principal upsert 2025-07-01 13:49:43 +02:00
Lennart
734455b5ab version 0.4.7 2025-06-30 20:05:01 +02:00
Lennart
8c6a616015 fix sync-collection limit element 2025-06-30 20:03:54 +02:00
Lennart K
828e7399c8 xml: Make serialization more ergonomic and clippy appeasement 2025-06-29 17:00:10 +02:00
Lennart K
891ef6a9f3 write test fixtures for sqlite store 2025-06-29 12:23:23 +02:00
Lennart
7b27ac22a4 Add Thunderbird to tested clients 2025-06-28 11:43:20 +02:00
25 changed files with 610 additions and 217 deletions

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n REPLACE INTO principals\n (id, displayname, principal_type, password_hash)\n VALUES (?, ?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "2f043f62a7c0eae1023e319f0bc8f35dfdcf6a8247e03b1de3e2cabb2d3ab8ae"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO principals\n (id, displayname, principal_type, password_hash) VALUES (?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n (displayname, principal_type, password_hash)\n = (excluded.displayname, excluded.principal_type, excluded.password_hash)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "5c09c2a3c052188435409d4ff076575394e625dd19f00dea2d4c71a9f34a5952"
}

378
Cargo.lock generated
View File

@@ -145,7 +145,7 @@ dependencies = [
"rustc-hash",
"serde",
"serde_derive",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -181,9 +181,134 @@ checksum = "34921de3d57974069bad483fdfe0ec65d88c4ff892edd1ab4d8b03be0dda1b9b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-channel"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"pin-project-lite",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
"async-channel 2.3.1",
"async-executor",
"async-io",
"async-lock",
"blocking",
"futures-lite",
"once_cell",
]
[[package]]
name = "async-io"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3"
dependencies = [
"async-lock",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite",
"parking",
"polling",
"rustix",
"slab",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.4.0",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24"
dependencies = [
"async-attributes",
"async-channel 1.9.0",
"async-global-executor",
"async-io",
"async-lock",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.88"
@@ -192,7 +317,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -377,6 +502,19 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [
"async-channel 2.3.1",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "bumpalo"
version = "3.19.0"
@@ -498,7 +636,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -630,7 +768,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -654,7 +792,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -665,7 +803,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -706,7 +844,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"unicode-xid",
]
@@ -730,7 +868,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -845,6 +983,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "etcetera"
version = "0.8.0"
@@ -856,6 +1004,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.4.0"
@@ -867,6 +1021,22 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener 5.4.0",
"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"
@@ -1002,6 +1172,19 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.31"
@@ -1010,7 +1193,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -1098,6 +1281,18 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "gloo-timers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "group"
version = "0.13.0"
@@ -1184,6 +1379,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hex"
version = "0.4.3"
@@ -1578,6 +1779,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1610,6 +1820,12 @@ 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"
@@ -1632,6 +1848,9 @@ name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
dependencies = [
"value-bag",
]
[[package]]
name = "lru-slab"
@@ -1876,7 +2095,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2116,7 +2335,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2189,7 +2408,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2204,6 +2423,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "pkcs1"
version = "0.7.5"
@@ -2231,6 +2461,21 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polling"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "potential_utf"
version = "0.1.2"
@@ -2290,7 +2535,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"version_check",
"yansi",
]
@@ -2315,7 +2560,7 @@ dependencies = [
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2482,7 +2727,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2672,7 +2917,7 @@ dependencies = [
"regex",
"relative-path",
"rustc_version",
"syn",
"syn 2.0.104",
"unicode-ident",
]
@@ -2684,7 +2929,7 @@ checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14"
dependencies = [
"quote",
"rand 0.8.5",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -2717,7 +2962,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"syn 2.0.104",
"walkdir",
]
@@ -2754,7 +2999,7 @@ dependencies = [
[[package]]
name = "rustical"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"anyhow",
"argon2",
@@ -2797,8 +3042,9 @@ dependencies = [
[[package]]
name = "rustical_caldav"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-std",
"async-trait",
"axum",
"axum-extra",
@@ -2812,6 +3058,7 @@ dependencies = [
"ical",
"percent-encoding",
"quick-xml",
"rstest",
"rustical_dav",
"rustical_dav_push",
"rustical_ical",
@@ -2833,7 +3080,7 @@ dependencies = [
[[package]]
name = "rustical_carddav"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-trait",
"axum",
@@ -2865,7 +3112,7 @@ dependencies = [
[[package]]
name = "rustical_dav"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-trait",
"axum",
@@ -2890,7 +3137,7 @@ dependencies = [
[[package]]
name = "rustical_dav_push"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-trait",
"axum",
@@ -2916,7 +3163,7 @@ dependencies = [
[[package]]
name = "rustical_frontend"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"askama",
"askama_web",
@@ -2949,7 +3196,7 @@ dependencies = [
[[package]]
name = "rustical_ical"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"axum",
"chrono",
@@ -2967,7 +3214,7 @@ dependencies = [
[[package]]
name = "rustical_oidc"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-trait",
"axum",
@@ -2982,7 +3229,7 @@ dependencies = [
[[package]]
name = "rustical_store"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"anyhow",
"async-trait",
@@ -3016,7 +3263,7 @@ dependencies = [
[[package]]
name = "rustical_store_sqlite"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"async-trait",
"chrono",
@@ -3024,6 +3271,7 @@ dependencies = [
"password-auth",
"password-hash",
"pbkdf2",
"rstest",
"rustical_ical",
"rustical_store",
"serde",
@@ -3036,13 +3284,26 @@ dependencies = [
[[package]]
name = "rustical_xml"
version = "0.4.6"
version = "0.4.8"
dependencies = [
"quick-xml",
"thiserror 2.0.12",
"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.28"
@@ -3164,7 +3425,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3247,7 +3508,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3381,7 +3642,7 @@ dependencies = [
"crc",
"crossbeam-queue",
"either",
"event-listener",
"event-listener 5.4.0",
"futures-core",
"futures-intrusive",
"futures-io",
@@ -3415,7 +3676,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3438,7 +3699,7 @@ dependencies = [
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn",
"syn 2.0.104",
"tokio",
"url",
]
@@ -3591,7 +3852,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3600,6 +3861,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.104"
@@ -3628,7 +3900,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3657,7 +3929,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3668,7 +3940,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -3763,7 +4035,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4013,7 +4285,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4181,6 +4453,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "value-bag"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -4255,7 +4533,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -4290,7 +4568,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4395,7 +4673,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4406,7 +4684,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4613,7 +4891,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4642,7 +4920,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"synstructure",
]
@@ -4663,7 +4941,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]
[[package]]
@@ -4683,7 +4961,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
"synstructure",
]
@@ -4723,5 +5001,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.104",
]

View File

@@ -2,7 +2,7 @@
members = ["crates/*"]
[workspace.package]
version = "0.4.6"
version = "0.4.8"
edition = "2024"
description = "A CalDAV server"
repository = "https://github.com/lennart-k/rustical"
@@ -139,6 +139,7 @@ ece = { version = "2.3", default-features = false, features = [
"backend-openssl",
] }
openssl = { version = "0.10", features = ["vendored"] }
async-std = { version = "1.13", features = ["attributes"] }
[dependencies]
rustical_store = { workspace = true }

View File

@@ -31,3 +31,4 @@ a CalDAV/CardDAV server
- Evolution
- Apple Calendar
- Home Assistant integration
- Thunderbird

View File

@@ -9,6 +9,8 @@ publish = false
[dev-dependencies]
rustical_store_sqlite = { workspace = true, features = ["test"] }
rstest.workspace = true
async-std.workspace = true
[dependencies]
axum.workspace = true

View File

@@ -1,33 +1,38 @@
use crate::principal::PrincipalResourceService;
use rustical_dav::resource::ResourceService;
use rustical_store::auth::{AuthenticationProvider, Principal, PrincipalType};
use rustical_store_sqlite::tests::get_test_stores;
use std::sync::Arc;
use crate::principal::PrincipalResourceService;
use rstest::rstest;
use rustical_dav::resource::ResourceService;
use rustical_store_sqlite::{
SqliteStore,
calendar_store::SqliteCalendarStore,
principal_store::SqlitePrincipalStore,
tests::{get_test_calendar_store, get_test_principal_store, get_test_subscription_store},
};
#[rstest]
#[tokio::test]
async fn test_principal_resource() {
let (_, cal_store, sub_store, auth_provider, _) = get_test_stores().await;
async fn test_principal_resource(
#[from(get_test_calendar_store)]
#[future]
cal_store: SqliteCalendarStore,
#[from(get_test_principal_store)]
#[future]
auth_provider: SqlitePrincipalStore,
#[from(get_test_subscription_store)]
#[future]
sub_store: SqliteStore,
) {
let service = PrincipalResourceService {
cal_store,
sub_store,
auth_provider: auth_provider.clone(),
cal_store: Arc::new(cal_store.await),
sub_store: Arc::new(sub_store.await),
auth_provider: Arc::new(auth_provider.await),
};
auth_provider
.insert_principal(
Principal {
id: "user".to_owned(),
displayname: None,
memberships: vec![],
password: None,
principal_type: PrincipalType::Individual,
},
true,
)
.await
.unwrap();
assert!(matches!(
service.get_resource(&("anonymous".to_owned(),), true).await,
service
.get_resource(&("invalid-user".to_owned(),), true)
.await,
Err(crate::Error::NotFound)
));
@@ -37,3 +42,5 @@ async fn test_principal_resource() {
.unwrap();
}
#[tokio::test]
async fn test_propfind() {}

View File

@@ -111,11 +111,10 @@ impl<T1: XmlSerialize, T2: XmlSerialize> axum::response::IntoResponse
fn into_response(self) -> axum::response::Response {
use axum::body::Body;
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
if let Err(err) = self.serialize_root(&mut writer) {
return crate::Error::from(err).into_response();
}
let output = match self.serialize_to_string() {
Ok(out) => out,
Err(err) => return crate::Error::from(err).into_response(),
};
let mut resp = axum::response::Response::builder().status(StatusCode::MULTI_STATUS);
let hdrs = resp.headers_mut().unwrap();

View File

@@ -23,20 +23,17 @@ mod tests {
#[test]
fn test_serialize_resourcetype() {
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
resourcetype: Resourcetype(&[
ResourcetypeInner(Some(crate::namespace::NS_DAV), "displayname"),
ResourcetypeInner(Some(crate::namespace::NS_CALENDARSERVER), "calendar-color"),
]),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(
out,
"<document><resourcetype><displayname xmlns=\"DAV:\"/><calendar-color xmlns=\"http://calendarserver.org/ns/\"/></resourcetype></document>"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<document><resourcetype><displayname xmlns=\"DAV:\"/><calendar-color xmlns=\"http://calendarserver.org/ns/\"/></resourcetype></document>"
)
}
}

View File

@@ -1,4 +1,4 @@
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize};
use rustical_xml::{ValueDeserialize, ValueSerialize, XmlDeserialize, XmlRootTag};
use super::PropfindType;
@@ -32,11 +32,35 @@ impl ValueSerialize for SyncLevel {
}
}
// https://datatracker.ietf.org/doc/html/rfc5323#section-5.17
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct LimitElement {
#[xml(ns = "crate::namespace::NS_DAV")]
pub nresults: NresultsElement,
}
impl From<u64> for LimitElement {
fn from(value: u64) -> Self {
Self {
nresults: NresultsElement(value),
}
}
}
impl From<LimitElement> for u64 {
fn from(value: LimitElement) -> Self {
value.nresults.0
}
}
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
pub struct NresultsElement(#[xml(ty = "text")] u64);
#[derive(XmlDeserialize, Clone, Debug, PartialEq, XmlRootTag)]
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
#[xml(ns = "crate::namespace::NS_DAV")]
#[xml(ns = "crate::namespace::NS_DAV", root = b"sync-collection")]
pub struct SyncCollectionRequest<PN: XmlDeserialize> {
#[xml(ns = "crate::namespace::NS_DAV")]
pub sync_token: String,
@@ -45,5 +69,48 @@ pub struct SyncCollectionRequest<PN: XmlDeserialize> {
#[xml(ns = "crate::namespace::NS_DAV", ty = "untagged")]
pub prop: PropfindType<PN>,
#[xml(ns = "crate::namespace::NS_DAV")]
pub limit: Option<u64>,
pub limit: Option<LimitElement>,
}
#[cfg(test)]
mod tests {
use crate::xml::{
PropElement, PropfindType,
sync_collection::{SyncCollectionRequest, SyncLevel},
};
use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlDocument};
const SYNC_COLLECTION_REQUEST: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<sync-collection xmlns="DAV:">
<sync-token />
<sync-level>1</sync-level>
<limit>
<nresults>100</nresults>
</limit>
<prop>
<getetag />
</prop>
</sync-collection>
"#;
#[derive(XmlDeserialize, PropName, EnumVariants, PartialEq)]
#[xml(unit_variants_ident = "TestPropName")]
enum TestProp {
Getetag(String),
}
#[test]
fn test_parse_sync_collection_request() {
let request =
SyncCollectionRequest::<TestPropName>::parse_str(SYNC_COLLECTION_REQUEST).unwrap();
assert_eq!(
request,
SyncCollectionRequest {
sync_token: "".to_owned(),
sync_level: SyncLevel::One,
prop: PropfindType::Prop(PropElement(vec![TestPropName::Getetag], vec![])),
limit: Some(100.into())
}
)
}
}

View File

@@ -99,13 +99,13 @@ impl<S: SubscriptionStore> DavPushController<S> {
content_update,
};
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
if let Err(err) = push_message.serialize_root(&mut writer) {
error!("Could not serialize push message: {}", err);
return;
}
let payload = String::from_utf8(output).unwrap();
let payload = match push_message.serialize_to_string() {
Ok(payload) => payload,
Err(err) => {
error!("Could not serialize push message: {}", err);
return;
}
};
for subsciption in subscriptions {
if let Some(allowed_push_servers) = &self.allowed_push_servers {

View File

@@ -19,6 +19,8 @@ export class CreateAddressbookForm extends LitElement {
@property()
user: String = ''
@property()
principal: String = ''
@property()
addr_id: String = ''
@property()
displayname: String = ''
@@ -34,6 +36,11 @@ export class CreateAddressbookForm extends LitElement {
<dialog ${ref(this.dialog)}>
<h3>Create addressbook</h3>
<form @submit=${this.submit} ${ref(this.form)}>
<label>
principal (for group addressbooks)
<input type="text" name="principal" value=${this.user} @change=${e => this.principal = e.target.value} />
</label>
<br>
<label>
id
<input type="text" name="id" @change=${e => this.addr_id = e.target.value} />
@@ -68,7 +75,7 @@ export class CreateAddressbookForm extends LitElement {
return
}
// TODO: Escape user input: There's not really a security risk here but would be nicer
await this.client.createDirectory(`/principal/${this.user}/${this.addr_id}`, {
await this.client.createDirectory(`/principal/${this.principal || this.user}/${this.addr_id}`, {
data: `
<mkcol xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
<set>

View File

@@ -18,6 +18,8 @@ export class CreateCalendarForm extends LitElement {
@property()
user: String = ''
@property()
principal: String = ''
@property()
cal_id: String = ''
@property()
displayname: String = ''
@@ -40,6 +42,11 @@ export class CreateCalendarForm extends LitElement {
<dialog ${ref(this.dialog)}>
<h3>Create calendar</h3>
<form @submit=${this.submit} ${ref(this.form)}>
<label>
principal (for group calendar)
<input type="text" name="principal" value=${this.user} @change=${e => this.principal = e.target.value} />
</label>
<br>
<label>
id
<input type="text" name="id" @change=${e => this.cal_id = e.target.value} />
@@ -94,7 +101,7 @@ export class CreateCalendarForm extends LitElement {
alert("No calendar components selected")
return
}
await this.client.createDirectory(`/principal/${this.user}/${this.cal_id}`, {
await this.client.createDirectory(`/principal/${this.principal || this.user}/${this.cal_id}`, {
data: `
<mkcol xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/">
<set>

View File

@@ -17,6 +17,7 @@ let CreateAddressbookForm = class extends i {
super();
this.client = an("/carddav");
this.user = "";
this.principal = "";
this.addr_id = "";
this.displayname = "";
this.description = "";
@@ -32,6 +33,11 @@ let CreateAddressbookForm = class extends i {
<dialog ${n(this.dialog)}>
<h3>Create addressbook</h3>
<form @submit=${this.submit} ${n(this.form)}>
<label>
principal (for group addressbooks)
<input type="text" name="principal" value=${this.user} @change=${(e2) => this.principal = e2.target.value} />
</label>
<br>
<label>
id
<input type="text" name="id" @change=${(e2) => this.addr_id = e2.target.value} />
@@ -68,7 +74,7 @@ let CreateAddressbookForm = class extends i {
alert("Empty displayname");
return;
}
await this.client.createDirectory(`/principal/${this.user}/${this.addr_id}`, {
await this.client.createDirectory(`/principal/${this.principal || this.user}/${this.addr_id}`, {
data: `
<mkcol xmlns="DAV:" xmlns:CARD="urn:ietf:params:xml:ns:carddav">
<set>
@@ -87,6 +93,9 @@ let CreateAddressbookForm = class extends i {
__decorateClass([
n$1()
], CreateAddressbookForm.prototype, "user", 2);
__decorateClass([
n$1()
], CreateAddressbookForm.prototype, "principal", 2);
__decorateClass([
n$1()
], CreateAddressbookForm.prototype, "addr_id", 2);

View File

@@ -17,6 +17,7 @@ let CreateCalendarForm = class extends i {
super();
this.client = an("/caldav");
this.user = "";
this.principal = "";
this.cal_id = "";
this.displayname = "";
this.description = "";
@@ -35,6 +36,11 @@ let CreateCalendarForm = class extends i {
<dialog ${n(this.dialog)}>
<h3>Create calendar</h3>
<form @submit=${this.submit} ${n(this.form)}>
<label>
principal (for group calendar)
<input type="text" name="principal" value=${this.user} @change=${(e2) => this.principal = e2.target.value} />
</label>
<br>
<label>
id
<input type="text" name="id" @change=${(e2) => this.cal_id = e2.target.value} />
@@ -92,7 +98,7 @@ let CreateCalendarForm = class extends i {
alert("No calendar components selected");
return;
}
await this.client.createDirectory(`/principal/${this.user}/${this.cal_id}`, {
await this.client.createDirectory(`/principal/${this.principal || this.user}/${this.cal_id}`, {
data: `
<mkcol xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/">
<set>
@@ -116,6 +122,9 @@ let CreateCalendarForm = class extends i {
__decorateClass([
n$1()
], CreateCalendarForm.prototype, "user", 2);
__decorateClass([
n$1()
], CreateCalendarForm.prototype, "principal", 2);
__decorateClass([
n$1()
], CreateCalendarForm.prototype, "cal_id", 2);

View File

@@ -8,7 +8,10 @@ license.workspace = true
publish = false
[features]
test = []
test = ["dep:rstest"]
[dev-dependencies]
rstest.workspace = true
[dependencies]
tokio.workspace = true
@@ -25,3 +28,4 @@ password-hash.workspace = true
uuid.workspace = true
pbkdf2.workspace = true
rustical_ical.workspace = true
rstest = { workspace = true, optional = true }

View File

@@ -114,9 +114,11 @@ impl AuthenticationProvider for SqlitePrincipalStore {
let password = user.password.map(Secret::into_inner);
sqlx::query!(
r#"
REPLACE INTO principals
(id, displayname, principal_type, password_hash)
VALUES (?, ?, ?, ?)
INSERT INTO principals
(id, displayname, principal_type, password_hash) VALUES (?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
(displayname, principal_type, password_hash)
= (excluded.displayname, excluded.principal_type, excluded.password_hash)
"#,
user.id,
user.displayname,

View File

@@ -2,41 +2,54 @@ use crate::{
SqliteStore, addressbook_store::SqliteAddressbookStore, calendar_store::SqliteCalendarStore,
principal_store::SqlitePrincipalStore,
};
use rustical_store::{
AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore,
auth::AuthenticationProvider,
};
use rustical_store::auth::{AuthenticationProvider, Principal, PrincipalType};
use sqlx::SqlitePool;
use std::sync::Arc;
use tokio::sync::mpsc::Receiver;
use tokio::sync::OnceCell;
pub async fn get_test_stores() -> (
Arc<impl AddressbookStore>,
Arc<impl CalendarStore>,
Arc<impl SubscriptionStore>,
Arc<impl AuthenticationProvider>,
Receiver<CollectionOperation>,
) {
let db = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::migrate!("./migrations").run(&db).await.unwrap();
// let db = create_db_pool("sqlite::memory:", true).await.unwrap();
// Channel to watch for changes (for DAV Push)
let (send, recv) = tokio::sync::mpsc::channel(1000);
static DB: OnceCell<SqlitePool> = OnceCell::const_new();
let addressbook_store = Arc::new(SqliteAddressbookStore::new(db.clone(), send.clone()));
let cal_store = Arc::new(SqliteCalendarStore::new(db.clone(), send));
let subscription_store = Arc::new(SqliteStore::new(db.clone()));
let principal_store = Arc::new(SqlitePrincipalStore::new(db.clone()));
(
addressbook_store,
cal_store,
subscription_store,
principal_store,
recv,
)
async fn get_test_db() -> SqlitePool {
DB.get_or_init(async || {
let db = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::migrate!("./migrations").run(&db).await.unwrap();
// Populate with test data
let principal_store = SqlitePrincipalStore::new(db.clone());
principal_store
.insert_principal(
Principal {
id: "user".to_owned(),
displayname: None,
memberships: vec![],
password: None,
principal_type: PrincipalType::Individual,
},
false,
)
.await
.unwrap();
db
})
.await
.clone()
}
#[tokio::test]
async fn test_create_store() {
get_test_stores().await;
#[rstest::fixture]
pub async fn get_test_addressbook_store() -> SqliteAddressbookStore {
let (send, _recv) = tokio::sync::mpsc::channel(1000);
SqliteAddressbookStore::new(get_test_db().await, send)
}
#[rstest::fixture]
pub async fn get_test_calendar_store() -> SqliteCalendarStore {
let (send, _recv) = tokio::sync::mpsc::channel(1000);
SqliteCalendarStore::new(get_test_db().await, send)
}
#[rstest::fixture]
pub async fn get_test_subscription_store() -> SqliteStore {
SqliteStore::new(get_test_db().await)
}
#[rstest::fixture]
pub async fn get_test_principal_store() -> SqlitePrincipalStore {
SqlitePrincipalStore::new(get_test_db().await)
}

View File

@@ -1,6 +1,6 @@
use crate::XmlRootTag;
use quick_xml::{
events::{attributes::Attribute, BytesStart, Event},
events::{BytesStart, Event, attributes::Attribute},
name::{Namespace, QName},
};
use std::collections::HashMap;
@@ -44,6 +44,13 @@ pub trait XmlSerializeRoot {
&self,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()>;
fn serialize_to_string(&self) -> std::io::Result<String> {
let mut buf: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
let mut writer = quick_xml::Writer::new(&mut buf);
self.serialize_root(&mut writer)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
}
impl<T: XmlSerialize + XmlRootTag> XmlSerializeRoot for T {

View File

@@ -198,6 +198,7 @@ fn test_struct_generics() {
#[derive(XmlDeserialize, XmlRootTag)]
#[xml(root = b"document")]
struct Document<T: XmlDeserialize> {
#[allow(dead_code)]
child: T,
}
@@ -218,6 +219,7 @@ fn test_struct_unparsed() {
#[derive(XmlDeserialize, XmlRootTag)]
#[xml(root = b"document")]
struct Document {
#[allow(dead_code)]
child: Unparsed,
}

View File

@@ -23,12 +23,16 @@ enum ExtensionProp {
enum CalendarProp {
// WebDAV (RFC 2518)
#[xml(ns = "NS_DAV")]
#[allow(dead_code)]
Displayname(Option<String>),
#[xml(ns = "NS_DAV")]
#[allow(dead_code)]
Getcontenttype(&'static str),
#[xml(ns = "NS_DAV", rename = b"principal-URL")]
#[allow(dead_code)]
PrincipalUrl,
#[allow(dead_code)]
Topic,
}

View File

@@ -4,29 +4,34 @@ use rustical_xml::{Unparsed, XmlDeserialize, XmlDocument, XmlRootTag};
fn test_propertyupdate() {
#[derive(XmlDeserialize)]
struct SetPropertyElement<T: XmlDeserialize> {
#[allow(dead_code)]
prop: T,
}
#[derive(XmlDeserialize)]
struct TagName {
#[xml(ty = "tag_name")]
#[allow(dead_code)]
name: String,
}
#[derive(XmlDeserialize)]
struct PropertyElement {
#[xml(ty = "untagged")]
#[allow(dead_code)]
property: TagName,
}
#[derive(XmlDeserialize)]
struct RemovePropertyElement {
#[allow(dead_code)]
prop: PropertyElement,
}
#[derive(XmlDeserialize)]
enum Operation<T: XmlDeserialize> {
Set(SetPropertyElement<T>),
#[allow(dead_code)]
Remove(RemovePropertyElement),
}
@@ -34,10 +39,11 @@ fn test_propertyupdate() {
#[xml(root = b"propertyupdate")]
struct PropertyupdateElement<T: XmlDeserialize> {
#[xml(ty = "untagged", flatten)]
#[allow(dead_code)]
operations: Vec<Operation<T>>,
}
let doc = PropertyupdateElement::<Unparsed>::parse_str(
PropertyupdateElement::<Unparsed>::parse_str(
r#"
<propertyupdate>
<set>

View File

@@ -11,17 +11,17 @@ fn test_struct_value_tagged() {
#[derive(Debug, XmlSerialize, PartialEq)]
enum Prop {
Test(String),
Hello(usize),
Unit,
// Hello(usize),
// Unit,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
prop: Prop::Test("asd".to_owned()),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(out, "<propfind><prop><test>asd</test></prop></propfind>");
assert_eq!(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<propfind><prop><test>asd</test></prop></propfind>"
);
}

View File

@@ -1,11 +1,7 @@
use std::{
borrow::{Borrow, Cow},
collections::HashMap,
};
use quick_xml::Writer;
use quick_xml::name::Namespace;
use rustical_xml::{XmlDocument, XmlRootTag, XmlSerialize, XmlSerializeRoot};
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
use std::collections::HashMap;
use xml_derive::XmlDeserialize;
#[test]
@@ -22,16 +18,13 @@ fn test_struct_document() {
text: String,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
child: Child {
text: "asd".to_owned(),
},
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
}
#[test]
@@ -51,17 +44,14 @@ fn test_struct_untagged_attr() {
text: String,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
name: "okay".to_owned(),
child: Child {
text: "asd".to_owned(),
},
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
}
#[test]
@@ -73,16 +63,16 @@ fn test_struct_value_tagged() {
num: usize,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
href: "okay".to_owned(),
num: 123,
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(out, "<document><href>okay</href><num>123</num></document>");
assert_eq!(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<document><href>okay</href><num>123</num></document>"
);
}
#[test]
@@ -94,15 +84,15 @@ fn test_struct_value_untagged() {
href: String,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
href: "okays".to_owned(),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(out, "<document>okays</document>");
assert_eq!(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<document>okays</document>"
);
}
#[test]
@@ -114,17 +104,14 @@ fn test_struct_vec() {
href: Vec<String>,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
href: vec!["okay".to_owned(), "wow".to_owned()],
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(
out,
"<document><href>okay</href><href>wow</href></document>"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<document><href>okay</href><href>wow</href></document>"
);
}
@@ -147,15 +134,15 @@ fn test_struct_serialize_with() {
val.to_uppercase().serialize(ns, tag, namespaces, writer)
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
let out = Document {
href: "okay".to_owned(),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(out, "<document><href>OKAY</href></document>");
assert_eq!(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<document><href>OKAY</href></document>"
);
}
#[test]
@@ -173,8 +160,6 @@ fn test_struct_tag_list() {
name: String,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
tags: vec![
Tag {
@@ -188,10 +173,8 @@ fn test_struct_tag_list() {
},
],
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
dbg!(out);
}
#[test]
@@ -205,15 +188,11 @@ fn test_struct_ns() {
child: String,
}
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
child: "hello!".to_string(),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
dbg!(out);
}
#[test]
@@ -227,16 +206,11 @@ fn test_struct_tuple() {
#[derive(Debug, XmlSerialize, PartialEq, Default)]
struct Child(#[xml(ty = "tag_name")] String, #[xml(ty = "text")] String);
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document {
child: Child("child".to_owned(), "Hello!".to_owned()),
}
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
dbg!(out);
}
#[test]
@@ -247,11 +221,7 @@ fn test_tuple_struct() {
#[xml(root = b"document")]
struct Document(#[xml(ns = "NS", rename = b"okay")] String);
let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf);
Document("hello!".to_string())
.serialize_root(&mut writer)
.serialize_to_string()
.unwrap();
let out = String::from_utf8(buf).unwrap();
dbg!(out);
}

View File

@@ -27,3 +27,4 @@ a CalDAV/CardDAV server
- Evolution
- Apple Calendar
- Home Assistant integration
- Thunderbird