From 9d1b50bc2f8c64833833c805f5ecd80e7b7e5981 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 15:16:04 +0200 Subject: [PATCH 01/15] add aws-sdk-sqs --- Cargo.lock | 230 +++++++++++++++++++++----- crates/bin/docs_rs_watcher/Cargo.toml | 1 + 2 files changed, 189 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c049e144..b1ceb81dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ dependencies = [ "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", + "aws-smithy-json 0.62.6", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -536,9 +536,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcd93c82209ac7413532388067dce79be5a8780c1786e5fae3df22e4dee2864" +checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -596,6 +596,30 @@ dependencies = [ "url", ] +[[package]] +name = "aws-sdk-sqs" +version = "1.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce61cf7e451891862a315dc96e1dbeb5e6a6f3740b354b5243217602b7e437b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.6", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + [[package]] name = "aws-sdk-sts" version = "1.103.0" @@ -606,7 +630,7 @@ dependencies = [ "aws-runtime", "aws-smithy-async", "aws-smithy-http 0.63.6", - "aws-smithy-json 0.62.5", + "aws-smithy-json 0.62.6", "aws-smithy-observability", "aws-smithy-query", "aws-smithy-runtime", @@ -623,9 +647,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68dc0b907359b120170613b5c09ccc61304eac3998ff6274b97d93ee6490115a" +checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -738,17 +762,23 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2", + "h2 0.3.27", + "h2 0.4.14", + "http 0.2.12", "http 1.4.0", - "hyper", - "hyper-rustls", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.9.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.9", "hyper-util", "pin-project-lite", - "rustls", + "rustls 0.21.12", + "rustls 0.23.40", "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower", "tracing", ] @@ -764,10 +794,12 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.5" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", ] @@ -792,15 +824,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" dependencies = [ "aws-smithy-async", "aws-smithy-http 0.63.6", "aws-smithy-http-client", "aws-smithy-observability", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "bytes", "fastrand", @@ -817,9 +850,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -844,11 +877,22 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.0", +] + [[package]] name = "aws-smithy-types" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" dependencies = [ "base64-simd", "bytes", @@ -891,13 +935,14 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.15" +version = "1.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" dependencies = [ "aws-credential-types", "aws-smithy-async", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "rustc_version", "tracing", @@ -917,7 +962,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "itoa 1.0.18", "matchit", @@ -2445,6 +2490,7 @@ name = "docs_rs_watcher" version = "0.6.0" dependencies = [ "anyhow", + "aws-sdk-sqs", "clap", "crates-index", "crates-index-diff", @@ -3946,6 +3992,25 @@ dependencies = [ "phf 0.11.3", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.14" @@ -4241,6 +4306,30 @@ dependencies = [ "typenum", ] +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.18", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.9.0" @@ -4251,7 +4340,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", + "h2 0.4.14", "http 1.4.0", "http-body 1.0.1", "httparse", @@ -4263,6 +4352,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.9" @@ -4270,12 +4374,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper", + "hyper 1.9.0", "hyper-util", - "rustls", + "rustls 0.23.40", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", ] @@ -4285,7 +4389,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -4300,7 +4404,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "native-tls", "tokio", @@ -4320,7 +4424,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-body 1.0.1", - "hyper", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", @@ -4980,7 +5084,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "log", "pin-project-lite", @@ -6025,7 +6129,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.40", "socket2 0.6.3", "thiserror", "tokio", @@ -6046,7 +6150,7 @@ dependencies = [ "rand 0.9.4", "ring", "rustc-hash", - "rustls", + "rustls 0.23.40", "rustls-pki-types", "slab", "thiserror", @@ -6336,12 +6440,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.4.14", "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-tls", "hyper-util", "js-sys", @@ -6351,7 +6455,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.40", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -6359,7 +6463,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -6445,6 +6549,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.40" @@ -6454,7 +6570,7 @@ dependencies = [ "aws-lc-rs", "once_cell", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.13", "subtle", "zeroize", ] @@ -6492,10 +6608,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls", + "rustls 0.23.40", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki", + "rustls-webpki 0.103.13", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -6508,6 +6624,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -6611,6 +6737,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -7738,13 +7874,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.40", "tokio", ] @@ -7823,7 +7969,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-timeout", "hyper-util", "percent-encoding", diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 0b0f9ba6a..e0f80e245 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } +aws-sdk-sqs = "1.99.0" clap = { workspace = true } # NOTE: on the new infra, switch back from `git-https-reqwest` to `git-https` (curl) once the curl version is new enough crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https-reqwest", "git-performance", "parallel"] } From fddeb9a511f039ab0412e83738d25d253cd5f1f5 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 15:40:36 +0200 Subject: [PATCH 02/15] add shared subcrate for event types --- Cargo.lock | 13 +- crates/lib/docs_rs_crates_io/Cargo.toml | 19 +++ crates/lib/docs_rs_crates_io/src/events.rs | 184 +++++++++++++++++++++ crates/lib/docs_rs_crates_io/src/lib.rs | 1 + 4 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 crates/lib/docs_rs_crates_io/Cargo.toml create mode 100644 crates/lib/docs_rs_crates_io/src/events.rs create mode 100644 crates/lib/docs_rs_crates_io/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b1ceb81dd..7dc2708d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2152,6 +2152,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "docs_rs_crates_io" +version = "0.1.0" +dependencies = [ + "semver", + "serde", + "serde_json", +] + [[package]] name = "docs_rs_database" version = "0.0.0" @@ -6988,9 +6997,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa 1.0.18", "memchr", diff --git a/crates/lib/docs_rs_crates_io/Cargo.toml b/crates/lib/docs_rs_crates_io/Cargo.toml new file mode 100644 index 000000000..d10606ee6 --- /dev/null +++ b/crates/lib/docs_rs_crates_io/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "docs_rs_crates_io" +version = "0.1.0" +description = "types & logic for the direct integration between docs.rs & crates.io" + +authors.workspace = true +license.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +semver = { version = "1.0.28", features = ["serde"] } + +[dev-dependencies] +serde_json = "1.0.150" + +[lints] +workspace = true diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs new file mode 100644 index 000000000..41a81dd17 --- /dev/null +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -0,0 +1,184 @@ +#![allow(clippy::disallowed_types)] + +use std::fmt; + +/// Identify a kind of change that occurred to a crate +#[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] +#[serde(tag = "type", content = "payload", rename_all = "snake_case")] +pub enum Change { + /// A crate version was added. + Added(CrateVersion), + /// A crate version was unyanked. + Unyanked(CrateVersion), + /// A crate version was yanked. + Yanked(CrateVersion), + /// The name of the crate whose file was deleted, which implies all versions were deleted as well. + CrateDeleted { name: String }, + /// A crate version was deleted. + VersionDeleted(CrateVersion), +} + +impl Change { + /// Return the added crate, if this is this kind of change. + pub fn added(&self) -> Option<&CrateVersion> { + match self { + Change::Added(v) => Some(v), + _ => None, + } + } + + /// Return the yanked crate, if this is this kind of change. + pub fn yanked(&self) -> Option<&CrateVersion> { + match self { + Change::Yanked(v) => Some(v), + _ => None, + } + } + + /// Return the unyanked crate, if this is this kind of change. + pub fn unyanked(&self) -> Option<&CrateVersion> { + match self { + Change::Unyanked(v) => Some(v), + _ => None, + } + } + + /// Return the deleted crate, if this is this kind of change. + pub fn crate_deleted(&self) -> Option<&str> { + match self { + Change::CrateDeleted { name, .. } => Some(name.as_str()), + _ => None, + } + } + + /// Return the deleted version crate, if this is this kind of change. + pub fn version_deleted(&self) -> Option<&CrateVersion> { + match self { + Change::VersionDeleted(v) => Some(v), + _ => None, + } + } +} + +impl fmt::Display for Change { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match *self { + Change::Added(_) => "added", + Change::Yanked(_) => "yanked", + Change::CrateDeleted { .. } => "crate deleted", + Change::VersionDeleted(_) => "version deleted", + Change::Unyanked(_) => "unyanked", + } + ) + } +} + +/// Pack all information we know about a change made to a version of a crate. +#[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] +pub struct CrateVersion { + /// The crate name, i.e. `clap`. + pub name: String, + /// is the release yanked? + pub yanked: bool, + /// The semantic version of the crate. + #[serde(rename = "vers")] + pub version: semver::Version, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn crate_version() -> CrateVersion { + CrateVersion { + name: "clap".into(), + yanked: false, + version: semver::Version::new(4, 5, 0), + } + } + + #[test] + fn crate_version_serializes_with_vers_field() { + let event = crate_version(); + + assert_eq!( + serde_json::to_value(&event).unwrap(), + json!({ + "name": "clap", + "yanked": false, + "vers": "4.5.0", + }) + ); + } + + #[test] + fn change_serializes_with_expected_variant_shapes() { + let crate_version = crate_version(); + + let cases = [ + ( + Change::Added(crate_version.clone()), + json!({ + "type": "added", + "payload": { + "name": "clap", + "yanked": false, + "vers": "4.5.0", + } + }), + ), + ( + Change::Unyanked(crate_version.clone()), + json!({ + "type": "unyanked", + "payload": { + "name": "clap", + "yanked": false, + "vers": "4.5.0", + } + }), + ), + ( + Change::Yanked(crate_version.clone()), + json!({ + "type": "yanked", + "payload": { + "name": "clap", + "yanked": false, + "vers": "4.5.0", + } + }), + ), + ( + Change::CrateDeleted { + name: "old-crate".into(), + }, + json!({ + "type": "crate_deleted", + "payload": { + "name": "old-crate" + } + }), + ), + ( + Change::VersionDeleted(crate_version), + json!({ + "type": "version_deleted", + "payload": { + "name": "clap", + "yanked": false, + "vers": "4.5.0", + } + }), + ), + ]; + + for (event, expected) in cases { + assert_eq!(serde_json::to_value(&event).unwrap(), expected); + } + } +} diff --git a/crates/lib/docs_rs_crates_io/src/lib.rs b/crates/lib/docs_rs_crates_io/src/lib.rs new file mode 100644 index 000000000..a9970c28f --- /dev/null +++ b/crates/lib/docs_rs_crates_io/src/lib.rs @@ -0,0 +1 @@ +pub mod events; From 87c438435e9996c40292844b35e68f542eb2488f Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 16:14:48 +0200 Subject: [PATCH 03/15] feat(events): add event envelope metadata Wrap typed change payloads in a conventional event envelope with id, occurred_at, source, and schema_version. --- crates/lib/docs_rs_crates_io/src/events.rs | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index 41a81dd17..d8eb10074 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -76,6 +76,22 @@ impl fmt::Display for Change { } } +/// A conventional event envelope for crate index changes. +#[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] +pub struct Event { + /// Unique event identifier for deduplication and tracing. + pub id: String, + /// Timestamp when the underlying change occurred, as an RFC 3339 string. + pub occurred_at: String, + /// System that emitted the event. + pub source: String, + /// Version of the serialized event schema. + pub schema_version: u32, + /// The typed change payload. + #[serde(flatten)] + pub change: Change, +} + /// Pack all information we know about a change made to a version of a crate. #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] pub struct CrateVersion { @@ -101,6 +117,16 @@ mod tests { } } + fn event(change: Change) -> Event { + Event { + id: "evt_123".into(), + occurred_at: "2026-05-22T12:34:56Z".into(), + source: "crates-index".into(), + schema_version: 1, + change, + } + } + #[test] fn crate_version_serializes_with_vers_field() { let event = crate_version(); @@ -181,4 +207,25 @@ mod tests { assert_eq!(serde_json::to_value(&event).unwrap(), expected); } } + + #[test] + fn event_serializes_with_minimum_metadata() { + let event = event(Change::CrateDeleted { + name: "old-crate".into(), + }); + + assert_eq!( + serde_json::to_value(&event).unwrap(), + json!({ + "id": "evt_123", + "occurred_at": "2026-05-22T12:34:56Z", + "source": "crates-index", + "schema_version": 1, + "type": "crate_deleted", + "payload": { + "name": "old-crate" + } + }) + ); + } } From b7403da2686a3c44caf5ce4e0927f8bc2b63d8b8 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 16:25:14 +0200 Subject: [PATCH 04/15] refactor(events): version event payload types Rename the current wire payload to ChangeV1 and make the event envelope generic for future schema versions. --- crates/lib/docs_rs_crates_io/src/events.rs | 49 ++++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index d8eb10074..d2cd795d2 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -5,7 +5,7 @@ use std::fmt; /// Identify a kind of change that occurred to a crate #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] #[serde(tag = "type", content = "payload", rename_all = "snake_case")] -pub enum Change { +pub enum ChangeV1 { /// A crate version was added. Added(CrateVersion), /// A crate version was unyanked. @@ -18,11 +18,11 @@ pub enum Change { VersionDeleted(CrateVersion), } -impl Change { +impl ChangeV1 { /// Return the added crate, if this is this kind of change. pub fn added(&self) -> Option<&CrateVersion> { match self { - Change::Added(v) => Some(v), + ChangeV1::Added(v) => Some(v), _ => None, } } @@ -30,7 +30,7 @@ impl Change { /// Return the yanked crate, if this is this kind of change. pub fn yanked(&self) -> Option<&CrateVersion> { match self { - Change::Yanked(v) => Some(v), + ChangeV1::Yanked(v) => Some(v), _ => None, } } @@ -38,7 +38,7 @@ impl Change { /// Return the unyanked crate, if this is this kind of change. pub fn unyanked(&self) -> Option<&CrateVersion> { match self { - Change::Unyanked(v) => Some(v), + ChangeV1::Unyanked(v) => Some(v), _ => None, } } @@ -46,7 +46,7 @@ impl Change { /// Return the deleted crate, if this is this kind of change. pub fn crate_deleted(&self) -> Option<&str> { match self { - Change::CrateDeleted { name, .. } => Some(name.as_str()), + ChangeV1::CrateDeleted { name, .. } => Some(name.as_str()), _ => None, } } @@ -54,23 +54,23 @@ impl Change { /// Return the deleted version crate, if this is this kind of change. pub fn version_deleted(&self) -> Option<&CrateVersion> { match self { - Change::VersionDeleted(v) => Some(v), + ChangeV1::VersionDeleted(v) => Some(v), _ => None, } } } -impl fmt::Display for Change { +impl fmt::Display for ChangeV1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { - Change::Added(_) => "added", - Change::Yanked(_) => "yanked", - Change::CrateDeleted { .. } => "crate deleted", - Change::VersionDeleted(_) => "version deleted", - Change::Unyanked(_) => "unyanked", + ChangeV1::Added(_) => "added", + ChangeV1::Yanked(_) => "yanked", + ChangeV1::CrateDeleted { .. } => "crate deleted", + ChangeV1::VersionDeleted(_) => "version deleted", + ChangeV1::Unyanked(_) => "unyanked", } ) } @@ -78,7 +78,7 @@ impl fmt::Display for Change { /// A conventional event envelope for crate index changes. #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] -pub struct Event { +pub struct Event { /// Unique event identifier for deduplication and tracing. pub id: String, /// Timestamp when the underlying change occurred, as an RFC 3339 string. @@ -89,9 +89,12 @@ pub struct Event { pub schema_version: u32, /// The typed change payload. #[serde(flatten)] - pub change: Change, + pub change: T, } +/// The first version of the public event wire format. +pub type EventV1 = Event; + /// Pack all information we know about a change made to a version of a crate. #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] pub struct CrateVersion { @@ -117,8 +120,8 @@ mod tests { } } - fn event(change: Change) -> Event { - Event { + fn event(change: ChangeV1) -> EventV1 { + EventV1 { id: "evt_123".into(), occurred_at: "2026-05-22T12:34:56Z".into(), source: "crates-index".into(), @@ -147,7 +150,7 @@ mod tests { let cases = [ ( - Change::Added(crate_version.clone()), + ChangeV1::Added(crate_version.clone()), json!({ "type": "added", "payload": { @@ -158,7 +161,7 @@ mod tests { }), ), ( - Change::Unyanked(crate_version.clone()), + ChangeV1::Unyanked(crate_version.clone()), json!({ "type": "unyanked", "payload": { @@ -169,7 +172,7 @@ mod tests { }), ), ( - Change::Yanked(crate_version.clone()), + ChangeV1::Yanked(crate_version.clone()), json!({ "type": "yanked", "payload": { @@ -180,7 +183,7 @@ mod tests { }), ), ( - Change::CrateDeleted { + ChangeV1::CrateDeleted { name: "old-crate".into(), }, json!({ @@ -191,7 +194,7 @@ mod tests { }), ), ( - Change::VersionDeleted(crate_version), + ChangeV1::VersionDeleted(crate_version), json!({ "type": "version_deleted", "payload": { @@ -210,7 +213,7 @@ mod tests { #[test] fn event_serializes_with_minimum_metadata() { - let event = event(Change::CrateDeleted { + let event = event(ChangeV1::CrateDeleted { name: "old-crate".into(), }); From e52cae891fa7d5f1349f613d7b50faf81b5add6f Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 16:32:57 +0200 Subject: [PATCH 05/15] refactor(events): use typed event timestamps Remove event source metadata and store occurred_at as an RFC 3339 OffsetDateTime. --- Cargo.lock | 1 + crates/lib/docs_rs_crates_io/Cargo.toml | 1 + crates/lib/docs_rs_crates_io/src/events.rs | 39 ++++++++++++++++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dc2708d6..be33873ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2159,6 +2159,7 @@ dependencies = [ "semver", "serde", "serde_json", + "time", ] [[package]] diff --git a/crates/lib/docs_rs_crates_io/Cargo.toml b/crates/lib/docs_rs_crates_io/Cargo.toml index d10606ee6..df404c935 100644 --- a/crates/lib/docs_rs_crates_io/Cargo.toml +++ b/crates/lib/docs_rs_crates_io/Cargo.toml @@ -11,6 +11,7 @@ edition.workspace = true [dependencies] serde = { version = "1.0.228", features = ["derive"] } semver = { version = "1.0.28", features = ["serde"] } +time = { version = "0.3.44", features = ["formatting", "parsing", "serde"] } [dev-dependencies] serde_json = "1.0.150" diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index d2cd795d2..34abfe92e 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -1,6 +1,7 @@ #![allow(clippy::disallowed_types)] use std::fmt; +use time::OffsetDateTime; /// Identify a kind of change that occurred to a crate #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] @@ -81,10 +82,9 @@ impl fmt::Display for ChangeV1 { pub struct Event { /// Unique event identifier for deduplication and tracing. pub id: String, - /// Timestamp when the underlying change occurred, as an RFC 3339 string. - pub occurred_at: String, - /// System that emitted the event. - pub source: String, + /// Timestamp when the underlying change occurred. + #[serde(with = "time::serde::rfc3339")] + pub occurred_at: OffsetDateTime, /// Version of the serialized event schema. pub schema_version: u32, /// The typed change payload. @@ -123,8 +123,11 @@ mod tests { fn event(change: ChangeV1) -> EventV1 { EventV1 { id: "evt_123".into(), - occurred_at: "2026-05-22T12:34:56Z".into(), - source: "crates-index".into(), + occurred_at: OffsetDateTime::parse( + "2026-05-22T12:34:56Z", + &time::format_description::well_known::Rfc3339, + ) + .unwrap(), schema_version: 1, change, } @@ -222,7 +225,6 @@ mod tests { json!({ "id": "evt_123", "occurred_at": "2026-05-22T12:34:56Z", - "source": "crates-index", "schema_version": 1, "type": "crate_deleted", "payload": { @@ -231,4 +233,27 @@ mod tests { }) ); } + + #[test] + fn event_deserializes_rfc3339_occurred_at() { + let event: EventV1 = serde_json::from_value(json!({ + "id": "evt_123", + "occurred_at": "2026-05-22T12:34:56Z", + "schema_version": 1, + "type": "crate_deleted", + "payload": { + "name": "old-crate" + } + })) + .unwrap(); + + assert_eq!( + event.occurred_at, + OffsetDateTime::parse( + "2026-05-22T12:34:56Z", + &time::format_description::well_known::Rfc3339, + ) + .unwrap() + ); + } } From 6b053a37c239139f875a6f8a64dcc3e62947597d Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 16:35:11 +0200 Subject: [PATCH 06/15] refactor(events): use chrono timestamps Replace time::OffsetDateTime with chrono::DateTime for RFC 3339 event timestamps. --- Cargo.lock | 4 +++- crates/lib/docs_rs_crates_io/Cargo.toml | 2 +- crates/lib/docs_rs_crates_io/src/events.rs | 21 ++++++++------------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be33873ff..d696e0ebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1290,8 +1290,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -2156,10 +2158,10 @@ dependencies = [ name = "docs_rs_crates_io" version = "0.1.0" dependencies = [ + "chrono", "semver", "serde", "serde_json", - "time", ] [[package]] diff --git a/crates/lib/docs_rs_crates_io/Cargo.toml b/crates/lib/docs_rs_crates_io/Cargo.toml index df404c935..a513e14ac 100644 --- a/crates/lib/docs_rs_crates_io/Cargo.toml +++ b/crates/lib/docs_rs_crates_io/Cargo.toml @@ -9,9 +9,9 @@ repository.workspace = true edition.workspace = true [dependencies] +chrono = { version = "0.4.42", features = ["serde"] } serde = { version = "1.0.228", features = ["derive"] } semver = { version = "1.0.28", features = ["serde"] } -time = { version = "0.3.44", features = ["formatting", "parsing", "serde"] } [dev-dependencies] serde_json = "1.0.150" diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index 34abfe92e..3dcf86760 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -1,7 +1,7 @@ #![allow(clippy::disallowed_types)] +use chrono::{DateTime, Utc}; use std::fmt; -use time::OffsetDateTime; /// Identify a kind of change that occurred to a crate #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] @@ -83,8 +83,7 @@ pub struct Event { /// Unique event identifier for deduplication and tracing. pub id: String, /// Timestamp when the underlying change occurred. - #[serde(with = "time::serde::rfc3339")] - pub occurred_at: OffsetDateTime, + pub occurred_at: DateTime, /// Version of the serialized event schema. pub schema_version: u32, /// The typed change payload. @@ -123,11 +122,9 @@ mod tests { fn event(change: ChangeV1) -> EventV1 { EventV1 { id: "evt_123".into(), - occurred_at: OffsetDateTime::parse( - "2026-05-22T12:34:56Z", - &time::format_description::well_known::Rfc3339, - ) - .unwrap(), + occurred_at: DateTime::parse_from_rfc3339("2026-05-22T12:34:56Z") + .unwrap() + .with_timezone(&Utc), schema_version: 1, change, } @@ -249,11 +246,9 @@ mod tests { assert_eq!( event.occurred_at, - OffsetDateTime::parse( - "2026-05-22T12:34:56Z", - &time::format_description::well_known::Rfc3339, - ) - .unwrap() + DateTime::parse_from_rfc3339("2026-05-22T12:34:56Z") + .unwrap() + .with_timezone(&Utc) ); } } From 0b7dd8da0419b32bbc47c840b9802582e7dfd2df Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 17:03:56 +0200 Subject: [PATCH 07/15] wider deps --- crates/lib/docs_rs_crates_io/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/lib/docs_rs_crates_io/Cargo.toml b/crates/lib/docs_rs_crates_io/Cargo.toml index a513e14ac..96b373c89 100644 --- a/crates/lib/docs_rs_crates_io/Cargo.toml +++ b/crates/lib/docs_rs_crates_io/Cargo.toml @@ -9,12 +9,12 @@ repository.workspace = true edition.workspace = true [dependencies] -chrono = { version = "0.4.42", features = ["serde"] } -serde = { version = "1.0.228", features = ["derive"] } -semver = { version = "1.0.28", features = ["serde"] } +chrono = { version = "0.4", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +semver = { version = "1", features = ["serde"] } [dev-dependencies] -serde_json = "1.0.150" +serde_json = "1.0" [lints] workspace = true From 277ea22f63faa20ff8cf8e141da148913effdf24 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 18:33:43 +0200 Subject: [PATCH 08/15] fix(watcher): make version delete idempotent Treat duplicate version deletion events as a no-op so temporary event-based handling can safely replay them. --- crates/bin/docs_rs_watcher/src/db/delete.rs | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/bin/docs_rs_watcher/src/db/delete.rs b/crates/bin/docs_rs_watcher/src/db/delete.rs index dbfa0e58e..742bbb634 100644 --- a/crates/bin/docs_rs_watcher/src/db/delete.rs +++ b/crates/bin/docs_rs_watcher/src/db/delete.rs @@ -170,14 +170,18 @@ async fn delete_version_from_database( format!("DELETE FROM {table} WHERE {column} IN (SELECT id FROM releases WHERE crate_id = $1 AND version = $2)").as_str()) .bind(crate_id).bind(version).execute(&mut *transaction).await?; } - let is_library: bool = sqlx::query_scalar!( + let Some(is_library) = sqlx::query_scalar!( "DELETE FROM releases WHERE crate_id = $1 AND version = $2 RETURNING is_library", crate_id.0, version as _, ) - .fetch_one(&mut *transaction) + .fetch_optional(&mut *transaction) .await? - .unwrap_or(false); + else { + transaction.commit().await?; + return Ok(false); + }; + let is_library = is_library.unwrap_or(false); sqlx::query!( "DELETE FROM queue WHERE name = $1 AND version = $2;", @@ -690,6 +694,32 @@ mod tests { Ok(()) } + #[tokio::test(flavor = "multi_thread")] + async fn test_delete_already_deleted_version_doesnt_error() -> Result<()> { + let env = TestEnvironment::new().await?; + let mut conn = env.async_conn().await?; + + env.fake_release() + .await + .name(&KRATE) + .version(V1) + .create() + .await?; + env.fake_release() + .await + .name(&KRATE) + .version(V2) + .create() + .await?; + + delete_version(&mut conn, env.storage()?, env.config(), &KRATE, &V1).await?; + delete_version(&mut conn, env.storage()?, env.config(), &KRATE, &V1).await?; + + assert!(crate_exists(&mut conn, &KRATE).await?); + + Ok(()) + } + #[tokio::test(flavor = "multi_thread")] async fn test_delete_version_waits_for_locked_queue_rows() -> Result<()> { let env = TestEnvironment::new().await?; From cb7453f3948d00a19834c4e3f9ac2d25605ea2bc Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 18:56:01 +0200 Subject: [PATCH 09/15] feat(watcher): add SQS config Add watcher config fields for an SQS queue URL and region to support an event-based path. --- crates/bin/docs_rs_watcher/src/config.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bin/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs index 7b5f17976..597549f29 100644 --- a/crates/bin/docs_rs_watcher/src/config.rs +++ b/crates/bin/docs_rs_watcher/src/config.rs @@ -7,6 +7,8 @@ use std::{path::PathBuf, time::Duration}; pub struct Config { pub registry_index_path: PathBuf, pub registry_url: Option, + pub sqs_queue_url: Option, + pub sqs_region: Option, /// How long to wait between registry checks pub delay_between_registry_fetches: Duration, @@ -29,6 +31,8 @@ impl AppConfig for Config { Ok(Self { registry_index_path: env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?, registry_url: maybe_env("REGISTRY_URL")?, + sqs_queue_url: maybe_env("DOCSRS_SQS_QUEUE_URL")?, + sqs_region: maybe_env("DOCSRS_SQS_REGION")?, delay_between_registry_fetches: Duration::from_secs(env::( "DOCSRS_DELAY_BETWEEN_REGISTRY_FETCHES", 60, From de207e532af1fea82d5d78718a3e4723cba48930 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 18:56:48 +0200 Subject: [PATCH 10/15] refactor(watcher): parse SQS queue URL Use url::Url for the watcher SQS queue URL config so invalid values fail during config loading. --- crates/bin/docs_rs_watcher/Cargo.toml | 1 + crates/bin/docs_rs_watcher/src/config.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index e0f80e245..38eb11199 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -33,6 +33,7 @@ rayon = "1.6.1" sqlx = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +url = { workspace = true } [dev-dependencies] docs_rs_config = { path = "../../lib/docs_rs_config", features = ["testing"] } diff --git a/crates/bin/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs index 597549f29..404ade8f5 100644 --- a/crates/bin/docs_rs_watcher/src/config.rs +++ b/crates/bin/docs_rs_watcher/src/config.rs @@ -2,12 +2,13 @@ use anyhow::Result; use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, time::Duration}; +use url::Url; #[derive(Debug)] pub struct Config { pub registry_index_path: PathBuf, pub registry_url: Option, - pub sqs_queue_url: Option, + pub sqs_queue_url: Option, pub sqs_region: Option, /// How long to wait between registry checks From b311a599d391230dc6d882de28b9089ec9b5bd6b Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 22 May 2026 18:57:05 +0200 Subject: [PATCH 11/15] chore(lockfile): record watcher url dep Update Cargo.lock after making docs_rs_watcher depend directly on url. --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index d696e0ebd..dfa12b8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2529,6 +2529,7 @@ dependencies = [ "test-case", "tokio", "tracing", + "url", ] [[package]] From e84c343e18c6b8d7c9aaebe9c93d18800f44572c Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 23 May 2026 08:15:59 +0200 Subject: [PATCH 12/15] refactor(events): drop schema version Remove the redundant schema_version field from the crates.io event envelope and keep versioning in the typed payloads. --- crates/lib/docs_rs_crates_io/src/events.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index 3dcf86760..5ba9b4abc 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -84,8 +84,6 @@ pub struct Event { pub id: String, /// Timestamp when the underlying change occurred. pub occurred_at: DateTime, - /// Version of the serialized event schema. - pub schema_version: u32, /// The typed change payload. #[serde(flatten)] pub change: T, @@ -125,7 +123,6 @@ mod tests { occurred_at: DateTime::parse_from_rfc3339("2026-05-22T12:34:56Z") .unwrap() .with_timezone(&Utc), - schema_version: 1, change, } } @@ -222,7 +219,6 @@ mod tests { json!({ "id": "evt_123", "occurred_at": "2026-05-22T12:34:56Z", - "schema_version": 1, "type": "crate_deleted", "payload": { "name": "old-crate" @@ -236,7 +232,6 @@ mod tests { let event: EventV1 = serde_json::from_value(json!({ "id": "evt_123", "occurred_at": "2026-05-22T12:34:56Z", - "schema_version": 1, "type": "crate_deleted", "payload": { "name": "old-crate" From 1e374cf4bfcfd5bdaf51ef8c8bc91e6e063f2b8b Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 23 May 2026 08:17:50 +0200 Subject: [PATCH 13/15] renames --- crates/lib/docs_rs_crates_io/src/events.rs | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index 5ba9b4abc..46f43b11a 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -6,7 +6,7 @@ use std::fmt; /// Identify a kind of change that occurred to a crate #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] #[serde(tag = "type", content = "payload", rename_all = "snake_case")] -pub enum ChangeV1 { +pub enum IndexChangeV1 { /// A crate version was added. Added(CrateVersion), /// A crate version was unyanked. @@ -19,11 +19,11 @@ pub enum ChangeV1 { VersionDeleted(CrateVersion), } -impl ChangeV1 { +impl IndexChangeV1 { /// Return the added crate, if this is this kind of change. pub fn added(&self) -> Option<&CrateVersion> { match self { - ChangeV1::Added(v) => Some(v), + IndexChangeV1::Added(v) => Some(v), _ => None, } } @@ -31,7 +31,7 @@ impl ChangeV1 { /// Return the yanked crate, if this is this kind of change. pub fn yanked(&self) -> Option<&CrateVersion> { match self { - ChangeV1::Yanked(v) => Some(v), + IndexChangeV1::Yanked(v) => Some(v), _ => None, } } @@ -39,7 +39,7 @@ impl ChangeV1 { /// Return the unyanked crate, if this is this kind of change. pub fn unyanked(&self) -> Option<&CrateVersion> { match self { - ChangeV1::Unyanked(v) => Some(v), + IndexChangeV1::Unyanked(v) => Some(v), _ => None, } } @@ -47,7 +47,7 @@ impl ChangeV1 { /// Return the deleted crate, if this is this kind of change. pub fn crate_deleted(&self) -> Option<&str> { match self { - ChangeV1::CrateDeleted { name, .. } => Some(name.as_str()), + IndexChangeV1::CrateDeleted { name, .. } => Some(name.as_str()), _ => None, } } @@ -55,42 +55,42 @@ impl ChangeV1 { /// Return the deleted version crate, if this is this kind of change. pub fn version_deleted(&self) -> Option<&CrateVersion> { match self { - ChangeV1::VersionDeleted(v) => Some(v), + IndexChangeV1::VersionDeleted(v) => Some(v), _ => None, } } } -impl fmt::Display for ChangeV1 { +impl fmt::Display for IndexChangeV1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { - ChangeV1::Added(_) => "added", - ChangeV1::Yanked(_) => "yanked", - ChangeV1::CrateDeleted { .. } => "crate deleted", - ChangeV1::VersionDeleted(_) => "version deleted", - ChangeV1::Unyanked(_) => "unyanked", + IndexChangeV1::Added(_) => "added", + IndexChangeV1::Yanked(_) => "yanked", + IndexChangeV1::CrateDeleted { .. } => "crate deleted", + IndexChangeV1::VersionDeleted(_) => "version deleted", + IndexChangeV1::Unyanked(_) => "unyanked", } ) } } -/// A conventional event envelope for crate index changes. +/// A conventional event envelope for our events between crates.io & docs.rs #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] pub struct Event { /// Unique event identifier for deduplication and tracing. pub id: String, - /// Timestamp when the underlying change occurred. + /// Timestamp when the event occured pub occurred_at: DateTime, - /// The typed change payload. + /// The typed payload. #[serde(flatten)] pub change: T, } /// The first version of the public event wire format. -pub type EventV1 = Event; +pub type IndexChangeEventV1 = Event; /// Pack all information we know about a change made to a version of a crate. #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] @@ -117,8 +117,8 @@ mod tests { } } - fn event(change: ChangeV1) -> EventV1 { - EventV1 { + fn event(change: IndexChangeV1) -> IndexChangeEventV1 { + IndexChangeEventV1 { id: "evt_123".into(), occurred_at: DateTime::parse_from_rfc3339("2026-05-22T12:34:56Z") .unwrap() @@ -147,7 +147,7 @@ mod tests { let cases = [ ( - ChangeV1::Added(crate_version.clone()), + IndexChangeV1::Added(crate_version.clone()), json!({ "type": "added", "payload": { @@ -158,7 +158,7 @@ mod tests { }), ), ( - ChangeV1::Unyanked(crate_version.clone()), + IndexChangeV1::Unyanked(crate_version.clone()), json!({ "type": "unyanked", "payload": { @@ -169,7 +169,7 @@ mod tests { }), ), ( - ChangeV1::Yanked(crate_version.clone()), + IndexChangeV1::Yanked(crate_version.clone()), json!({ "type": "yanked", "payload": { @@ -180,7 +180,7 @@ mod tests { }), ), ( - ChangeV1::CrateDeleted { + IndexChangeV1::CrateDeleted { name: "old-crate".into(), }, json!({ @@ -191,7 +191,7 @@ mod tests { }), ), ( - ChangeV1::VersionDeleted(crate_version), + IndexChangeV1::VersionDeleted(crate_version), json!({ "type": "version_deleted", "payload": { @@ -210,7 +210,7 @@ mod tests { #[test] fn event_serializes_with_minimum_metadata() { - let event = event(ChangeV1::CrateDeleted { + let event = event(IndexChangeV1::CrateDeleted { name: "old-crate".into(), }); @@ -229,7 +229,7 @@ mod tests { #[test] fn event_deserializes_rfc3339_occurred_at() { - let event: EventV1 = serde_json::from_value(json!({ + let event: IndexChangeEventV1 = serde_json::from_value(json!({ "id": "evt_123", "occurred_at": "2026-05-22T12:34:56Z", "type": "crate_deleted", From bb4d8456ea0a98fa7151c98809d9b18f5bd89112 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 23 May 2026 08:22:02 +0200 Subject: [PATCH 14/15] some cleanup --- crates/lib/docs_rs_crates_io/src/events.rs | 44 +--------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/crates/lib/docs_rs_crates_io/src/events.rs b/crates/lib/docs_rs_crates_io/src/events.rs index 46f43b11a..f01933db6 100644 --- a/crates/lib/docs_rs_crates_io/src/events.rs +++ b/crates/lib/docs_rs_crates_io/src/events.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Utc}; use std::fmt; -/// Identify a kind of change that occurred to a crate +/// A change that can happen to a crate on our index. #[derive(Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] #[serde(tag = "type", content = "payload", rename_all = "snake_case")] pub enum IndexChangeV1 { @@ -19,48 +19,6 @@ pub enum IndexChangeV1 { VersionDeleted(CrateVersion), } -impl IndexChangeV1 { - /// Return the added crate, if this is this kind of change. - pub fn added(&self) -> Option<&CrateVersion> { - match self { - IndexChangeV1::Added(v) => Some(v), - _ => None, - } - } - - /// Return the yanked crate, if this is this kind of change. - pub fn yanked(&self) -> Option<&CrateVersion> { - match self { - IndexChangeV1::Yanked(v) => Some(v), - _ => None, - } - } - - /// Return the unyanked crate, if this is this kind of change. - pub fn unyanked(&self) -> Option<&CrateVersion> { - match self { - IndexChangeV1::Unyanked(v) => Some(v), - _ => None, - } - } - - /// Return the deleted crate, if this is this kind of change. - pub fn crate_deleted(&self) -> Option<&str> { - match self { - IndexChangeV1::CrateDeleted { name, .. } => Some(name.as_str()), - _ => None, - } - } - - /// Return the deleted version crate, if this is this kind of change. - pub fn version_deleted(&self) -> Option<&CrateVersion> { - match self { - IndexChangeV1::VersionDeleted(v) => Some(v), - _ => None, - } - } -} - impl fmt::Display for IndexChangeV1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( From c0436b166914cf1326d6e7104961ea9a477e0378 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 23 May 2026 09:22:13 +0200 Subject: [PATCH 15/15] no rustls --- Cargo.lock | 158 +++++--------------------- crates/bin/docs_rs_watcher/Cargo.toml | 2 +- 2 files changed, 27 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfa12b8ed..cf62a1150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -762,23 +762,17 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2 0.3.27", - "h2 0.4.14", - "http 0.2.12", + "h2", "http 1.4.0", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper 1.9.0", - "hyper-rustls 0.24.2", - "hyper-rustls 0.27.9", + "hyper", + "hyper-rustls", "hyper-util", "pin-project-lite", - "rustls 0.21.12", - "rustls 0.23.40", + "rustls", "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower", "tracing", ] @@ -962,7 +956,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "itoa 1.0.18", "matchit", @@ -4005,25 +3999,6 @@ dependencies = [ "phf 0.11.3", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.14" @@ -4319,30 +4294,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa 1.0.18", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.9.0" @@ -4353,7 +4304,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", + "h2", "http 1.4.0", "http-body 1.0.1", "httparse", @@ -4365,21 +4316,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.9" @@ -4387,12 +4323,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper 1.9.0", + "hyper", "hyper-util", - "rustls 0.23.40", + "rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", ] @@ -4402,7 +4338,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.9.0", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -4417,7 +4353,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -4437,7 +4373,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-body 1.0.1", - "hyper 1.9.0", + "hyper", "ipnet", "libc", "percent-encoding", @@ -5097,7 +5033,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "log", "pin-project-lite", @@ -6142,7 +6078,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.40", + "rustls", "socket2 0.6.3", "thiserror", "tokio", @@ -6163,7 +6099,7 @@ dependencies = [ "rand 0.9.4", "ring", "rustc-hash", - "rustls 0.23.40", + "rustls", "rustls-pki-types", "slab", "thiserror", @@ -6453,12 +6389,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.14", + "h2", "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", - "hyper-rustls 0.27.9", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -6468,7 +6404,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.40", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -6476,7 +6412,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -6562,18 +6498,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.40" @@ -6583,7 +6507,7 @@ dependencies = [ "aws-lc-rs", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki", "subtle", "zeroize", ] @@ -6621,10 +6545,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.40", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.13", + "rustls-webpki", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -6637,16 +6561,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.13" @@ -6750,16 +6664,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -7887,23 +7791,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.40", + "rustls", "tokio", ] @@ -7982,7 +7876,7 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 38eb11199..caeaefa97 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -8,7 +8,7 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } -aws-sdk-sqs = "1.99.0" +aws-sdk-sqs = { version = "1.99.0", default-features = false, features = ["default-https-client", "rt-tokio"] } clap = { workspace = true } # NOTE: on the new infra, switch back from `git-https-reqwest` to `git-https` (curl) once the curl version is new enough crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https-reqwest", "git-performance", "parallel"] }