diff --git a/.gitignore b/.gitignore index 8b30577..5927861 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ DATA.md AGENTS.md .agents/ .cargo/windows.toml +btls-extension-order-validation \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3c90168..8a4965f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,14 +80,14 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn", + "syn 2.0.119", ] [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "brotli" @@ -143,9 +143,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -218,22 +218,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "crc32fast" version = "1.5.0" @@ -248,9 +232,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "displaydoc" @@ -260,14 +241,14 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encoding_rs" @@ -334,13 +315,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -385,36 +366,36 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-task", @@ -435,9 +416,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "hashbrown" @@ -462,9 +443,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -472,9 +453,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -485,9 +466,9 @@ dependencies = [ [[package]] name = "http2" -version = "0.5.19" +version = "0.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39fa01ed3820381df693c455ba2109d229c0cb02c95a51108ef45ddd758d91b9" +checksum = "92d3114be2f413b2e491e686b93a28cda30c355cffc8d091a57f8be4b1342896" dependencies = [ "atomic-waker", "bytes", @@ -510,12 +491,13 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -523,9 +505,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -536,9 +518,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -550,15 +532,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -570,15 +552,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -602,9 +584,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -661,9 +643,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -692,9 +674,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" +checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" dependencies = [ "hashbrown", ] @@ -720,7 +702,7 @@ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -753,9 +735,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -774,9 +756,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "openssl-macros" @@ -786,7 +768,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -847,18 +829,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -890,7 +872,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn", + "syn 2.0.119", ] [[package]] @@ -910,9 +892,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -922,9 +904,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -945,9 +927,9 @@ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" [[package]] name = "rustversion" @@ -969,9 +951,9 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -979,22 +961,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1022,9 +1004,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "indexmap", "itoa", @@ -1075,9 +1057,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "slab" @@ -1093,9 +1075,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys", @@ -1109,9 +1091,20 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1135,28 +1128,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", + "syn 2.0.119", ] [[package]] @@ -1176,17 +1148,16 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "time" -version = "0.3.45" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -1196,15 +1167,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -1222,9 +1193,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -1249,13 +1220,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1272,13 +1243,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -1353,7 +1325,7 @@ checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1403,9 +1375,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -1438,35 +1410,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1504,7 +1447,6 @@ dependencies = [ "serde_json", "socket2", "sync_wrapper", - "system-configuration", "tokio", "tokio-btls", "tokio-socks", @@ -1513,7 +1455,6 @@ dependencies = [ "tower-http", "url", "webpki-root-certs", - "windows-registry", "wreq-proto", "wreq-rt", ] @@ -1611,7 +1552,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1632,7 +1573,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -1666,14 +1607,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 61a2ecf..194defe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ name = "wreq_ruby" [dependencies] magnus = { version = "0.8.2", features = ["bytes"] } rb-sys = { version = "0.9.128", default-features = false } -tokio = { version = "1.52.3", features = ["full"] } +tokio = { version = "1.53.1", features = ["full"] } wreq = { version = "6.0.0-rc", features = [ "query", "form", @@ -29,12 +29,11 @@ wreq = { version = "6.0.0-rc", features = [ "brotli", "deflate", "zstd", - "system-proxy", ] } wreq-util = { version = "3.0.0-rc", features = ["emulation-compression"] } -serde = { version = "1.0.228", features = ["derive"] } +serde = { version = "1.0.229", features = ["derive"] } serde_ignored = "0.1.14" -serde_json = { version = "1.0.150", features = [ +serde_json = { version = "1.0.151", features = [ "arbitrary_precision", "preserve_order", ] } @@ -44,8 +43,8 @@ cookie = "0.18.1" bytes = "1.12.1" arc-swap = "1.9.1" http = "1.4.1" -http-body-util = "0.1.3" -futures-util = { version = "0.3.32", default-features = false } +http-body-util = "0.1.4" +futures-util = { version = "0.3.33", default-features = false } [target.'cfg(unix)'.dependencies] forkguard = { version = "0.1.3", features = ["atfork"] } diff --git a/lib/wreq.rb b/lib/wreq.rb index 18ccb36..62820f1 100644 --- a/lib/wreq.rb +++ b/lib/wreq.rb @@ -50,8 +50,12 @@ module Wreq # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -84,8 +88,12 @@ def self.request(method, url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -118,8 +126,12 @@ def self.get(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -152,8 +164,12 @@ def self.head(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -186,8 +202,12 @@ def self.post(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -220,8 +240,12 @@ def self.put(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -254,8 +278,12 @@ def self.delete(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -288,8 +316,12 @@ def self.options(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -322,8 +354,12 @@ def self.trace(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the client's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. diff --git a/lib/wreq_ruby/client.rb b/lib/wreq_ruby/client.rb index e2ff2a3..dfc96ed 100644 --- a/lib/wreq_ruby/client.rb +++ b/lib/wreq_ruby/client.rb @@ -70,31 +70,40 @@ class Client # used to store and retrieve cookies for all requests made by this # client. Typically used together with `cookie_store: true`. # - # @param timeout [Integer, nil] Overall timeout for the entire request + # @param timeout [Numeric, nil] Overall timeout for the entire request # in seconds, including connection establishment, request transmission, - # and response reading. If not set, requests may wait indefinitely. - # - # @param connect_timeout [Integer, nil] Maximum time in seconds to wait - # when establishing a connection to the remote server. This is separate - # from the overall timeout. - # - # @param read_timeout [Integer, nil] Maximum time in seconds to wait - # between reading chunks of data from the server. Applies to each - # read operation, not the entire response. - # - # @param tcp_keepalive [Integer, nil] Time in seconds that a connection - # must be idle before TCP keepalive probes are sent. Helps detect - # broken connections. - # - # @param tcp_keepalive_interval [Integer, nil] Time in seconds between - # individual TCP keepalive probes. Only relevant if tcp_keepalive is set. + # and response reading. Fractional seconds are accepted. The value must + # be finite and non-negative; 0 expires immediately. Nil or omission + # leaves the timeout unset. + # + # @param connect_timeout [Numeric, nil] Maximum time in seconds to wait + # when establishing a connection to the remote server. Fractional seconds + # are accepted. The value must be finite and non-negative; 0 expires + # immediately. Nil or omission leaves the timeout unset. + # + # @param read_timeout [Numeric, nil] Maximum time in seconds to wait + # between reading chunks of data from the server. Fractional seconds are + # accepted. The value must be finite and non-negative; 0 expires + # immediately. Nil or omission leaves the timeout unset. + # + # @param tcp_keepalive [Numeric, nil] Time in seconds that a connection + # must be idle before TCP keepalive probes are sent. Fractional seconds + # are accepted. The value must be finite and non-negative; 0 is passed + # through as a zero duration. Nil or omission leaves the option unset. + # + # @param tcp_keepalive_interval [Numeric, nil] Time in seconds between + # individual TCP keepalive probes. Fractional seconds are accepted. The + # value must be finite and non-negative; 0 is passed through as a zero + # duration. Nil or omission leaves the option unset. # # @param tcp_keepalive_retries [Integer, nil] Number of failed keepalive # probes before the connection is considered dead and closed. # - # @param tcp_user_timeout [Integer, nil] Maximum time in seconds that + # @param tcp_user_timeout [Numeric, nil] Maximum time in seconds that # transmitted data may remain unacknowledged before the connection is - # forcibly closed. Available on Android, Fuchsia, and Linux only. + # forcibly closed. Fractional seconds are accepted. The value must be + # finite and non-negative; 0 is passed through as a zero duration. Nil or + # omission leaves the option unset. Available on Android, Fuchsia, and Linux only. # # @param tcp_nodelay [Boolean, nil] Enable TCP_NODELAY socket option, # which disables Nagle's algorithm. When true, small packets are sent @@ -105,9 +114,10 @@ class Client # allowing the reuse of local addresses in TIME_WAIT state. Useful for # reducing port exhaustion in high-throughput scenarios. # - # @param pool_idle_timeout [Integer, nil] Time in seconds before idle - # connections in the pool are evicted and closed. Helps free up - # resources for long-running applications. + # @param pool_idle_timeout [Numeric, nil] Time in seconds before idle + # connections in the pool are evicted and closed. Fractional seconds are + # accepted. The value must be finite and non-negative; 0 expires idle + # entries immediately. Nil or omission leaves the timeout unset. # # @param pool_max_idle_per_host [Integer, nil] Maximum number of idle # connections to maintain per host in the connection pool. Connections @@ -270,8 +280,12 @@ def self.new(**options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -306,8 +320,12 @@ def request(method, url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -340,8 +358,12 @@ def get(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -374,8 +396,12 @@ def head(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -408,8 +434,12 @@ def post(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -442,8 +472,12 @@ def put(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -476,8 +510,12 @@ def delete(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -510,8 +548,12 @@ def options(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. @@ -544,8 +586,12 @@ def trace(url, **options) # @param brotli [Boolean, nil] Enable Brotli compression # @param deflate [Boolean, nil] Enable deflate compression # @param zstd [Boolean, nil] Enable Zstandard compression - # @param timeout [Integer, nil] Total request timeout (seconds) - # @param read_timeout [Integer, nil] Per-chunk read timeout (seconds) + # @param timeout [Numeric, nil] Total request timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. + # @param read_timeout [Numeric, nil] Per-chunk read timeout in seconds. + # Must be finite and non-negative; fractions are accepted, 0 expires + # immediately, and nil leaves it unset. # @param proxy [String, nil] Proxy server URI # @param local_address [String, nil] Bind the request's local source IP address (IPv4/IPv6). Useful on multi-homed hosts to originate connections from a specific address or enforce source routing. Examples: "192.168.1.10", "10.0.0.5", "2001:db8::1". The address must exist on the host and be routable or the connection may fail. # @param interface [String, nil] Bind to an interface on supported platforms; unsupported platforms raise ArgumentError. diff --git a/src/client.rs b/src/client.rs index 6f8bf85..ce1c781 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,7 @@ mod query; mod req; pub mod resp; -use std::{net::IpAddr, time::Duration}; +use std::net::IpAddr; use ::serde::Deserialize; use magnus::{Module, Object, RModule, Ruby, TryConvert, Value, function, method, typed_data::Obj}; @@ -22,6 +22,7 @@ use crate::{ http::Method, options::{NativeOption, Options}, rt, + time::Duration, }; /// A builder for `Client`. @@ -54,31 +55,38 @@ struct Builder { cookie_provider: NativeOption, // ========= Timeout options ========= - /// The timeout to use for the client. (in seconds) - timeout: Option, - /// The connect timeout to use for the client. (in seconds) - connect_timeout: Option, - /// The read timeout to use for the client. (in seconds) - read_timeout: Option, + /// Overall timeout for a request, including connection and response body. + #[serde(default)] + timeout: NativeOption, + /// Maximum duration allowed to establish a connection. + #[serde(default)] + connect_timeout: NativeOption, + /// Maximum idle duration between response body reads. + #[serde(default)] + read_timeout: NativeOption, // ========= TCP options ========= - /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration. (in seconds) - tcp_keepalive: Option, - /// Set the interval between TCP keepalive probes. (in seconds) - tcp_keepalive_interval: Option, + /// Idle duration before TCP keepalive probes begin. + #[serde(default)] + tcp_keepalive: NativeOption, + /// Interval between TCP keepalive probes. + #[serde(default)] + tcp_keepalive_interval: NativeOption, /// Set the number of retries for TCP keepalive. tcp_keepalive_retries: Option, - /// Set an optional user timeout for TCP sockets. (in seconds) + /// Maximum duration for which transmitted data may remain unacknowledged. + #[serde(default)] #[allow(dead_code)] - tcp_user_timeout: Option, + tcp_user_timeout: NativeOption, /// Set that all sockets have `NO_DELAY` set. tcp_nodelay: Option, /// Set that all sockets have `SO_REUSEADDR` set. tcp_reuse_address: Option, // ========= Connection pool options ========= - /// Set an optional timeout for idle sockets being kept-alive. (in seconds) - pool_idle_timeout: Option, + /// Maximum idle duration before a pooled connection is evicted. + #[serde(default)] + pool_idle_timeout: NativeOption, /// Sets the maximum idle connection per host allowed in the pool. pool_max_idle_per_host: Option, /// Sets the maximum number of connections in the pool. @@ -175,6 +183,16 @@ impl Builder { cookie_provider, Obj => |value| (*value).clone() ); + + // Duration options. + extract_native_option!(options, builder, timeout); + extract_native_option!(options, builder, connect_timeout); + extract_native_option!(options, builder, read_timeout); + extract_native_option!(options, builder, tcp_keepalive); + extract_native_option!(options, builder, tcp_keepalive_interval); + extract_native_option!(options, builder, tcp_user_timeout); + extract_native_option!(options, builder, pool_idle_timeout); + builder .proxy .set(Extractor::::try_convert(options.as_value())?.into_inner()); @@ -277,18 +295,16 @@ impl Client { // TCP options. apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.tcp_keepalive, - tcp_keepalive, - Duration::from_secs + tcp_keepalive ); apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.tcp_keepalive_interval, - tcp_keepalive_interval, - Duration::from_secs + tcp_keepalive_interval ); apply_option!( set_if_some, @@ -298,11 +314,10 @@ impl Client { ); #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.tcp_user_timeout, - tcp_user_timeout, - Duration::from_secs + tcp_user_timeout ); apply_option!(set_if_some, builder, params.tcp_nodelay, tcp_nodelay); apply_option!( @@ -313,35 +328,26 @@ impl Client { ); // Timeout options. + apply_option!(set_if_some_inner, builder, params.timeout, timeout); apply_option!( - set_if_some_map, - builder, - params.timeout, - timeout, - Duration::from_secs - ); - apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.connect_timeout, - connect_timeout, - Duration::from_secs + connect_timeout ); apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.read_timeout, - read_timeout, - Duration::from_secs + read_timeout ); // Pool options. apply_option!( - set_if_some_map, + set_if_some_inner, builder, params.pool_idle_timeout, - pool_idle_timeout, - Duration::from_secs + pool_idle_timeout ); apply_option!( set_if_some, diff --git a/src/client/req.rs b/src/client/req.rs index 0603d9e..353bb63 100644 --- a/src/client/req.rs +++ b/src/client/req.rs @@ -1,4 +1,4 @@ -use std::{net::IpAddr, time::Duration}; +use std::net::IpAddr; use ::serde::Deserialize; use http::header; @@ -17,6 +17,7 @@ use crate::{ http::{Method, Version}, options::{NativeOption, Options}, rt, + time::Duration, }; /// The parameters for a request. @@ -38,11 +39,13 @@ pub struct Request { #[allow(dead_code)] interface: Option, - /// The timeout to use for the request. - timeout: Option, + /// Overall timeout for this request, overriding the client default. + #[serde(default)] + timeout: NativeOption, - /// The read timeout to use for the request. - read_timeout: Option, + /// Maximum idle duration between body reads, overriding the client default. + #[serde(default)] + read_timeout: NativeOption, /// The HTTP version to use for the request. #[serde(default)] @@ -133,6 +136,8 @@ impl Request { Obj => |value| (*value).clone() ); extract_native_option!(options, builder, version); + extract_native_option!(options, builder, timeout); + extract_native_option!(options, builder, read_timeout); extract_native_option!(options, builder, headers); extract_native_option!(options, builder, orig_headers); extract_native_option!(options, builder, cookies); @@ -202,19 +207,12 @@ pub fn execute_request>( ); // Timeout options. + apply_option!(set_if_some_inner, builder, request.timeout, timeout); apply_option!( - set_if_some_map, - builder, - request.timeout, - timeout, - Duration::from_secs - ); - apply_option!( - set_if_some_map, + set_if_some_inner, builder, request.read_timeout, - read_timeout, - Duration::from_secs + read_timeout ); // Network options. diff --git a/src/lib.rs b/src/lib.rs index 82d852d..ffe35ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ mod http; mod options; mod rt; mod serde; +mod time; use magnus::{Error, Module, Ruby, Value}; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..a2ae8c6 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,56 @@ +//! Ruby time-value conversions shared by native binding modules. + +use std::time::Duration as StdDuration; + +use magnus::numeric::NumericValue; +use magnus::{Error, Integer, Ruby, TryConvert, Value, value::ReprValue}; + +use crate::error::argument_error; + +/// A crate-wide duration converted from non-negative Ruby `Numeric` seconds. +/// +/// Ruby integers are converted directly so the full `u64` seconds range is +/// retained. Other numeric values are converted through `f64` and then rounded +/// to the nanosecond precision of [`StdDuration`]. +pub(crate) struct Duration(pub(crate) StdDuration); + +impl TryConvert for Duration { + /// Convert integer or fractional Ruby seconds without string coercion. + /// + /// # Errors + /// + /// Returns `TypeError` for non-numeric values and `ArgumentError` for + /// negative, non-finite, or out-of-range durations. + fn try_convert(value: Value) -> Result { + let ruby = Ruby::get_with(value); + let numeric = NumericValue::try_convert(value)?; + + // `u64::try_convert` starts with `Integer::try_convert`. Probe with + // `from_value` so a fractional Numeric takes the fallback without + // using `TypeError` as control flow or repeating the Integer check. + if let Some(integer) = Integer::from_value(numeric.as_value()) { + return integer + .to_u64() + .map(StdDuration::from_secs) + .map(Self) + .map_err(|_| invalid_duration(&ruby)); + } + + // `Float::try_convert(...).to_f64()` takes a Ruby Float detour. The + // `rb_num2dbl`-backed conversion yields the primitive required by + // `StdDuration` directly from the already-checked Numeric. + f64::try_convert(numeric.as_value()) + .and_then(|seconds| { + StdDuration::try_from_secs_f64(seconds).map_err(|_| invalid_duration(&ruby)) + }) + .map(Self) + } +} + +/// Build the shared Ruby error for a numeric value outside `Duration`'s domain. +fn invalid_duration(ruby: &Ruby) -> Error { + argument_error( + ruby, + "duration must be finite, non-negative, and within the supported range", + ) +} diff --git a/test/timeout_test.rb b/test/timeout_test.rb new file mode 100644 index 0000000..eb734bc --- /dev/null +++ b/test/timeout_test.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require "test_helper" +require "socket" + +class TimeoutTest < Minitest::Test + SUBSECOND_TIMEOUT = 0.25 + SERVER_DELAY = 1.2 + + def test_client_duration_options_accept_numeric_seconds + [1, 0.125, Rational(1, 8), 0, nil].each do |value| + options = client_duration_options.to_h { |name| [name, value] } + + assert_instance_of Wreq::Client, Wreq::Client.new(**options) + end + end + + def test_each_client_duration_option_rejects_negative_seconds + client_duration_options.each do |name| + error = assert_raises(ArgumentError) do + Wreq::Client.new(**{name => -0.25}) + end + + assert_includes error.message, ":#{name}" + end + end + + def test_duration_rejects_invalid_numeric_seconds + invalid_values = [ + -1, + Float::NAN, + Float::INFINITY, + -Float::INFINITY, + Float::MAX, + 2**256 + ] + + invalid_values.each do |value| + error = assert_raises(ArgumentError) do + Wreq::Client.new(timeout: value) + end + + assert_includes error.message, ":timeout" + end + end + + def test_duration_rejects_non_numeric_values + error = assert_raises(TypeError) do + Wreq::Client.new(timeout: "0.25") + end + + assert_includes error.message, ":timeout" + end + + def test_request_duration_options_reject_invalid_values_before_network_io + invalid_values = [ + -1, + -0.25, + Float::NAN, + Float::INFINITY, + -Float::INFINITY, + Float::MAX, + 2**256, + "0.25" + ] + + %i[timeout read_timeout].each do |name| + invalid_values.each do |value| + error_class = value.is_a?(String) ? TypeError : ArgumentError + error = assert_raises(error_class) do + Wreq.get("not a url", **{name => value}) + end + + assert_includes error.message, ":#{name}" + end + end + end + + def test_request_timeouts_accept_integer_and_nil_values + with_http_server do |url| + response = Wreq.get(url, timeout: 1, read_timeout: 1) + + assert_equal 200, response.code + assert_equal "ok", response.text + end + + with_http_server do |url| + response = Wreq.get(url, timeout: nil, read_timeout: nil) + + assert_equal 200, response.code + assert_equal "ok", response.text + end + end + + def test_client_fractional_timeout_preserves_subsecond_value + client = Wreq::Client.new(timeout: SUBSECOND_TIMEOUT) + + with_http_server(response_delay: SERVER_DELAY) do |url| + assert_fractional_timeout { client.get(url) } + end + end + + def test_request_timeout_override_preserves_subsecond_value + client = Wreq::Client.new(timeout: 2) + + with_http_server(response_delay: SERVER_DELAY) do |url| + assert_fractional_timeout do + client.get(url, timeout: SUBSECOND_TIMEOUT) + end + end + end + + def test_request_read_timeout_override_preserves_subsecond_value + client = Wreq::Client.new(read_timeout: 2) + + with_http_server(body_delay: SERVER_DELAY) do |url| + assert_fractional_timeout do + client.get(url, read_timeout: SUBSECOND_TIMEOUT).text + end + end + end + + def test_zero_request_timeouts_expire_immediately + client = Wreq::Client.new + + with_http_server(response_delay: SERVER_DELAY) do |url| + assert_immediate_timeout { client.get(url, timeout: 0) } + end + + with_http_server(body_delay: SERVER_DELAY) do |url| + assert_immediate_timeout do + client.get(url, read_timeout: 0).text + end + end + end + + private + + def client_duration_options + options = %i[ + timeout + connect_timeout + read_timeout + tcp_keepalive + tcp_keepalive_interval + pool_idle_timeout + ] + if RUBY_PLATFORM.match?(/linux|android|fuchsia/) + options << :tcp_user_timeout + end + options + end + + def assert_fractional_timeout + elapsed = measure_elapsed do + assert_raises(Wreq::TimeoutError) { yield } + end + + assert_operator elapsed, :>=, 0.1, + "Fractional timeout fired too early after #{elapsed.round(3)} seconds" + assert_operator elapsed, :<, 0.8, + "Fractional timeout fired too late after #{elapsed.round(3)} seconds" + end + + def assert_immediate_timeout + elapsed = measure_elapsed do + assert_raises(Wreq::TimeoutError) { yield } + end + + assert_operator elapsed, :<, 0.5, + "Zero timeout did not expire immediately (#{elapsed.round(3)} seconds)" + end + + def measure_elapsed + started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield + Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at + end + + def with_http_server(response_delay: 0, body_delay: 0) + server = TCPServer.new("127.0.0.1", 0) + thread = Thread.new do + socket = server.accept + read_request(socket) + sleep response_delay if response_delay.positive? + + socket.write( + "HTTP/1.1 200 OK\r\n" \ + "Content-Length: 2\r\n" \ + "Connection: close\r\n\r\n" + ) + sleep body_delay if body_delay.positive? + socket.write("ok") + rescue IOError, SystemCallError + nil + ensure + socket&.close + server.close unless server.closed? + end + thread.report_on_exception = false + + yield "http://127.0.0.1:#{server.addr[1]}/" + ensure + server&.close unless server&.closed? + thread&.kill + thread&.join(1) + end + + def read_request(socket) + while (line = socket.gets) + break if line == "\r\n" + end + end +end