Add a startup test to check whether existing data will be compatible with v0.12

This commit is contained in:
Lennart
2026-01-10 13:22:49 +01:00
parent 55eabfde4a
commit 53f81a9433
4 changed files with 165 additions and 12 deletions

82
Cargo.lock generated
View File

@@ -623,7 +623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3"
dependencies = [ dependencies = [
"chrono", "chrono",
"phf", "phf 0.12.1",
] ]
[[package]] [[package]]
@@ -1768,6 +1768,22 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "ical"
version = "0.11.0"
source = "git+https://github.com/lennart-k/ical-rs?branch=dev#64c342e7258ba445dc91b47cd8e20e0ac8ffc417"
dependencies = [
"chrono",
"chrono-tz",
"derive_more",
"itertools 0.14.0",
"lazy_static",
"phf 0.13.1",
"regex",
"rrule",
"thiserror 2.0.17",
]
[[package]] [[package]]
name = "ical" name = "ical"
version = "0.11.0" version = "0.11.0"
@@ -2593,7 +2609,18 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [ dependencies = [
"phf_shared", "phf_shared 0.12.1",
]
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_macros",
"phf_shared 0.13.1",
"serde",
] ]
[[package]] [[package]]
@@ -2602,8 +2629,8 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61" checksum = "efbdcb6f01d193b17f0b9c3360fa7e0e620991b193ff08702f78b3ce365d7e61"
dependencies = [ dependencies = [
"phf_generator", "phf_generator 0.12.1",
"phf_shared", "phf_shared 0.12.1",
] ]
[[package]] [[package]]
@@ -2613,7 +2640,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b" checksum = "2cbb1126afed61dd6368748dae63b1ee7dc480191c6262a3b4ff1e29d86a6c5b"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"phf_shared", "phf_shared 0.12.1",
]
[[package]]
name = "phf_generator"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_macros"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
dependencies = [
"phf_generator 0.13.1",
"phf_shared 0.13.1",
"proc-macro2",
"quote",
"syn 2.0.112",
] ]
[[package]] [[package]]
@@ -2625,6 +2675,15 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.10" version = "1.1.10"
@@ -3284,6 +3343,7 @@ dependencies = [
"figment", "figment",
"headers", "headers",
"http", "http",
"ical 0.11.0 (git+https://github.com/lennart-k/ical-rs?branch=dev)",
"insta", "insta",
"opentelemetry", "opentelemetry",
"opentelemetry-otlp", "opentelemetry-otlp",
@@ -3331,7 +3391,7 @@ dependencies = [
"futures-util", "futures-util",
"headers", "headers",
"http", "http",
"ical", "ical 0.11.0 (git+https://github.com/lennart-k/ical-rs)",
"insta", "insta",
"percent-encoding", "percent-encoding",
"quick-xml", "quick-xml",
@@ -3370,7 +3430,7 @@ dependencies = [
"derive_more", "derive_more",
"futures-util", "futures-util",
"http", "http",
"ical", "ical 0.11.0 (git+https://github.com/lennart-k/ical-rs)",
"insta", "insta",
"percent-encoding", "percent-encoding",
"quick-xml", "quick-xml",
@@ -3403,7 +3463,7 @@ dependencies = [
"futures-util", "futures-util",
"headers", "headers",
"http", "http",
"ical", "ical 0.11.0 (git+https://github.com/lennart-k/ical-rs)",
"itertools 0.14.0", "itertools 0.14.0",
"log", "log",
"matchit 0.9.1", "matchit 0.9.1",
@@ -3487,7 +3547,7 @@ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",
"derive_more", "derive_more",
"ical", "ical 0.11.0 (git+https://github.com/lennart-k/ical-rs)",
"regex", "regex",
"rrule", "rrule",
"rstest", "rstest",
@@ -3528,7 +3588,7 @@ dependencies = [
"futures-core", "futures-core",
"headers", "headers",
"http", "http",
"ical", "ical 0.11.0 (git+https://github.com/lennart-k/ical-rs)",
"regex", "regex",
"rrule", "rrule",
"rstest", "rstest",
@@ -4910,7 +4970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6728de8767c8dea44f41b88115a205ed23adc3302e1b4342be59d922934dae5" checksum = "f6728de8767c8dea44f41b88115a205ed23adc3302e1b4342be59d922934dae5"
dependencies = [ dependencies = [
"glob", "glob",
"phf", "phf 0.12.1",
"phf_codegen", "phf_codegen",
] ]

View File

@@ -201,3 +201,7 @@ tower-http.workspace = true
axum-extra.workspace = true axum-extra.workspace = true
headers.workspace = true headers.workspace = true
http.workspace = true http.workspace = true
# TODO: Remove in next major release
ical_dev = { package = "ical", git = "https://github.com/lennart-k/ical-rs", branch = "dev", features = [
"chrono-tz",
] }

View File

@@ -25,7 +25,7 @@ use std::sync::Arc;
use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Receiver;
use tower::Layer; use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer; use tower_http::normalize_path::NormalizePathLayer;
use tracing::info; use tracing::{info, warn};
mod app; mod app;
mod commands; mod commands;
@@ -34,6 +34,9 @@ mod config;
pub mod integration_tests; pub mod integration_tests;
mod setup_tracing; mod setup_tracing;
mod migration_0_12;
use migration_0_12::{validate_address_objects_0_12, validate_calendar_objects_0_12};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
@@ -115,6 +118,12 @@ async fn main() -> Result<()> {
let (addr_store, cal_store, subscription_store, principal_store, update_recv) = let (addr_store, cal_store, subscription_store, principal_store, update_recv) =
get_data_stores(!args.no_migrations, &config.data_store).await?; get_data_stores(!args.no_migrations, &config.data_store).await?;
warn!(
"Validating calendar data against the next-version ical parser.\nIn the next major release these will be rejected and cause errors.\nIf any errors occur, please open an issue so they can be fixed before the next major release."
);
validate_calendar_objects_0_12(principal_store.as_ref(), cal_store.as_ref()).await?;
validate_address_objects_0_12(principal_store.as_ref(), addr_store.as_ref()).await?;
let mut tasks = vec![]; let mut tasks = vec![];
if config.dav_push.enabled { if config.dav_push.enabled {

80
src/migration_0_12.rs Normal file
View File

@@ -0,0 +1,80 @@
use rustical_store::{AddressbookStore, CalendarStore, auth::AuthenticationProvider};
use tracing::{error, info};
pub async fn validate_calendar_objects_0_12(
principal_store: &impl AuthenticationProvider,
cal_store: &impl CalendarStore,
) -> Result<(), rustical_store::Error> {
let mut success = true;
for principal in principal_store.get_principals().await? {
for calendar in cal_store.get_calendars(&principal.id).await? {
for object in cal_store
.get_objects(&calendar.principal, &calendar.id)
.await?
{
if let Err(err) =
ical_dev::parser::ical::IcalObjectParser::new(object.get_ics().as_bytes())
.expect_one()
{
success = false;
error!(
"An error occured parsing a calendar object: principal={principal}, calendar={calendar}, object_id={object_id}: {err}",
principal = principal.id,
calendar = calendar.id,
object_id = object.get_id()
);
println!("{}", object.get_ics());
}
}
}
}
if success {
info!("Your calendar data seems to be valid in the next major version.");
} else {
error!(
"Not all calendar objects will be successfully parsed in the next major version (v0.12).
This will not cause issues in this version, but please comment under the tracking issue on GitHub:
https://github.com/lennart-k/rustical/issues/165"
);
}
Ok(())
}
pub async fn validate_address_objects_0_12(
principal_store: &impl AuthenticationProvider,
addr_store: &impl AddressbookStore,
) -> Result<(), rustical_store::Error> {
let mut success = true;
for principal in principal_store.get_principals().await? {
for addressbook in addr_store.get_addressbooks(&principal.id).await? {
for object in addr_store
.get_objects(&addressbook.principal, &addressbook.id)
.await?
{
if let Err(err) =
ical_dev::parser::vcard::VcardParser::new(object.get_vcf().as_bytes())
.expect_one()
{
success = false;
error!(
"An error occured parsing an address object: principal={principal}, addressbook={addressbook}, object_id={object_id}: {err}",
principal = principal.id,
addressbook = addressbook.id,
object_id = object.get_id()
);
println!("{}", object.get_vcf());
}
}
}
}
if success {
info!("Your addressbook data seems to be valid in the next major version.");
} else {
error!(
"Not all address objects will be successfully parsed in the next major version (v0.12).
This will not cause issues in this version, but please comment under the tracking issue on GitHub:
https://github.com/lennart-k/rustical/issues/165"
);
}
Ok(())
}