├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── conformance ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── buf.gen.yaml ├── conformance.yaml ├── gen │ └── connectrpc.conformance.v1.rs ├── run.sh └── src │ └── main.rs └── src ├── common.rs ├── lib.rs ├── metadata.rs ├── request.rs ├── request └── builder.rs ├── reqwest.rs ├── response.rs ├── response ├── builder.rs └── error.rs └── stream.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "atomic-waker" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 31 | 32 | [[package]] 33 | name = "backtrace" 34 | version = "0.3.74" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 37 | dependencies = [ 38 | "addr2line", 39 | "cfg-if", 40 | "libc", 41 | "miniz_oxide", 42 | "object", 43 | "rustc-demangle", 44 | "windows-targets", 45 | ] 46 | 47 | [[package]] 48 | name = "base64" 49 | version = "0.22.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "2.6.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 58 | 59 | [[package]] 60 | name = "bumpalo" 61 | version = "3.16.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 64 | 65 | [[package]] 66 | name = "bytes" 67 | version = "1.7.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 70 | 71 | [[package]] 72 | name = "cc" 73 | version = "1.1.25" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8" 76 | dependencies = [ 77 | "shlex", 78 | ] 79 | 80 | [[package]] 81 | name = "cfg-if" 82 | version = "1.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 85 | 86 | [[package]] 87 | name = "connect-rpc" 88 | version = "0.1.0" 89 | dependencies = [ 90 | "base64", 91 | "bytes", 92 | "form_urlencoded", 93 | "futures-util", 94 | "http", 95 | "http-body", 96 | "http-body-util", 97 | "percent-encoding", 98 | "reqwest", 99 | "serde", 100 | "serde_json", 101 | "thiserror", 102 | "tracing", 103 | ] 104 | 105 | [[package]] 106 | name = "core-foundation" 107 | version = "0.9.4" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 110 | dependencies = [ 111 | "core-foundation-sys", 112 | "libc", 113 | ] 114 | 115 | [[package]] 116 | name = "core-foundation-sys" 117 | version = "0.8.7" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 120 | 121 | [[package]] 122 | name = "encoding_rs" 123 | version = "0.8.34" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 126 | dependencies = [ 127 | "cfg-if", 128 | ] 129 | 130 | [[package]] 131 | name = "equivalent" 132 | version = "1.0.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 135 | 136 | [[package]] 137 | name = "errno" 138 | version = "0.3.9" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 141 | dependencies = [ 142 | "libc", 143 | "windows-sys 0.52.0", 144 | ] 145 | 146 | [[package]] 147 | name = "fastrand" 148 | version = "2.1.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 151 | 152 | [[package]] 153 | name = "fnv" 154 | version = "1.0.7" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 157 | 158 | [[package]] 159 | name = "foreign-types" 160 | version = "0.3.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 163 | dependencies = [ 164 | "foreign-types-shared", 165 | ] 166 | 167 | [[package]] 168 | name = "foreign-types-shared" 169 | version = "0.1.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 172 | 173 | [[package]] 174 | name = "form_urlencoded" 175 | version = "1.2.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 178 | dependencies = [ 179 | "percent-encoding", 180 | ] 181 | 182 | [[package]] 183 | name = "futures-channel" 184 | version = "0.3.30" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 187 | dependencies = [ 188 | "futures-core", 189 | ] 190 | 191 | [[package]] 192 | name = "futures-core" 193 | version = "0.3.31" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 196 | 197 | [[package]] 198 | name = "futures-io" 199 | version = "0.3.31" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 202 | 203 | [[package]] 204 | name = "futures-macro" 205 | version = "0.3.31" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 208 | dependencies = [ 209 | "proc-macro2", 210 | "quote", 211 | "syn", 212 | ] 213 | 214 | [[package]] 215 | name = "futures-sink" 216 | version = "0.3.31" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 219 | 220 | [[package]] 221 | name = "futures-task" 222 | version = "0.3.31" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 225 | 226 | [[package]] 227 | name = "futures-util" 228 | version = "0.3.31" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 231 | dependencies = [ 232 | "futures-core", 233 | "futures-io", 234 | "futures-macro", 235 | "futures-sink", 236 | "futures-task", 237 | "memchr", 238 | "pin-project-lite", 239 | "pin-utils", 240 | "slab", 241 | ] 242 | 243 | [[package]] 244 | name = "getrandom" 245 | version = "0.2.15" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 248 | dependencies = [ 249 | "cfg-if", 250 | "libc", 251 | "wasi", 252 | ] 253 | 254 | [[package]] 255 | name = "gimli" 256 | version = "0.31.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 259 | 260 | [[package]] 261 | name = "h2" 262 | version = "0.4.6" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 265 | dependencies = [ 266 | "atomic-waker", 267 | "bytes", 268 | "fnv", 269 | "futures-core", 270 | "futures-sink", 271 | "http", 272 | "indexmap", 273 | "slab", 274 | "tokio", 275 | "tokio-util", 276 | "tracing", 277 | ] 278 | 279 | [[package]] 280 | name = "hashbrown" 281 | version = "0.15.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 284 | 285 | [[package]] 286 | name = "hermit-abi" 287 | version = "0.3.9" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 290 | 291 | [[package]] 292 | name = "http" 293 | version = "1.1.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 296 | dependencies = [ 297 | "bytes", 298 | "fnv", 299 | "itoa", 300 | ] 301 | 302 | [[package]] 303 | name = "http-body" 304 | version = "1.0.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 307 | dependencies = [ 308 | "bytes", 309 | "http", 310 | ] 311 | 312 | [[package]] 313 | name = "http-body-util" 314 | version = "0.1.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 317 | dependencies = [ 318 | "bytes", 319 | "futures-util", 320 | "http", 321 | "http-body", 322 | "pin-project-lite", 323 | ] 324 | 325 | [[package]] 326 | name = "httparse" 327 | version = "1.9.5" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 330 | 331 | [[package]] 332 | name = "hyper" 333 | version = "1.4.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 336 | dependencies = [ 337 | "bytes", 338 | "futures-channel", 339 | "futures-util", 340 | "h2", 341 | "http", 342 | "http-body", 343 | "httparse", 344 | "itoa", 345 | "pin-project-lite", 346 | "smallvec", 347 | "tokio", 348 | "want", 349 | ] 350 | 351 | [[package]] 352 | name = "hyper-rustls" 353 | version = "0.27.3" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 356 | dependencies = [ 357 | "futures-util", 358 | "http", 359 | "hyper", 360 | "hyper-util", 361 | "rustls", 362 | "rustls-pki-types", 363 | "tokio", 364 | "tokio-rustls", 365 | "tower-service", 366 | ] 367 | 368 | [[package]] 369 | name = "hyper-tls" 370 | version = "0.6.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 373 | dependencies = [ 374 | "bytes", 375 | "http-body-util", 376 | "hyper", 377 | "hyper-util", 378 | "native-tls", 379 | "tokio", 380 | "tokio-native-tls", 381 | "tower-service", 382 | ] 383 | 384 | [[package]] 385 | name = "hyper-util" 386 | version = "0.1.9" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" 389 | dependencies = [ 390 | "bytes", 391 | "futures-channel", 392 | "futures-util", 393 | "http", 394 | "http-body", 395 | "hyper", 396 | "pin-project-lite", 397 | "socket2", 398 | "tokio", 399 | "tower-service", 400 | "tracing", 401 | ] 402 | 403 | [[package]] 404 | name = "idna" 405 | version = "0.5.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 408 | dependencies = [ 409 | "unicode-bidi", 410 | "unicode-normalization", 411 | ] 412 | 413 | [[package]] 414 | name = "indexmap" 415 | version = "2.6.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 418 | dependencies = [ 419 | "equivalent", 420 | "hashbrown", 421 | ] 422 | 423 | [[package]] 424 | name = "ipnet" 425 | version = "2.10.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 428 | 429 | [[package]] 430 | name = "itoa" 431 | version = "1.0.11" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 434 | 435 | [[package]] 436 | name = "js-sys" 437 | version = "0.3.70" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 440 | dependencies = [ 441 | "wasm-bindgen", 442 | ] 443 | 444 | [[package]] 445 | name = "libc" 446 | version = "0.2.159" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 449 | 450 | [[package]] 451 | name = "linux-raw-sys" 452 | version = "0.4.14" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 455 | 456 | [[package]] 457 | name = "log" 458 | version = "0.4.22" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 461 | 462 | [[package]] 463 | name = "memchr" 464 | version = "2.7.4" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 467 | 468 | [[package]] 469 | name = "mime" 470 | version = "0.3.17" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 473 | 474 | [[package]] 475 | name = "miniz_oxide" 476 | version = "0.8.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 479 | dependencies = [ 480 | "adler2", 481 | ] 482 | 483 | [[package]] 484 | name = "mio" 485 | version = "1.0.2" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 488 | dependencies = [ 489 | "hermit-abi", 490 | "libc", 491 | "wasi", 492 | "windows-sys 0.52.0", 493 | ] 494 | 495 | [[package]] 496 | name = "native-tls" 497 | version = "0.2.12" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 500 | dependencies = [ 501 | "libc", 502 | "log", 503 | "openssl", 504 | "openssl-probe", 505 | "openssl-sys", 506 | "schannel", 507 | "security-framework", 508 | "security-framework-sys", 509 | "tempfile", 510 | ] 511 | 512 | [[package]] 513 | name = "object" 514 | version = "0.36.5" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 517 | dependencies = [ 518 | "memchr", 519 | ] 520 | 521 | [[package]] 522 | name = "once_cell" 523 | version = "1.20.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" 526 | dependencies = [ 527 | "portable-atomic", 528 | ] 529 | 530 | [[package]] 531 | name = "openssl" 532 | version = "0.10.66" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 535 | dependencies = [ 536 | "bitflags", 537 | "cfg-if", 538 | "foreign-types", 539 | "libc", 540 | "once_cell", 541 | "openssl-macros", 542 | "openssl-sys", 543 | ] 544 | 545 | [[package]] 546 | name = "openssl-macros" 547 | version = "0.1.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 550 | dependencies = [ 551 | "proc-macro2", 552 | "quote", 553 | "syn", 554 | ] 555 | 556 | [[package]] 557 | name = "openssl-probe" 558 | version = "0.1.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 561 | 562 | [[package]] 563 | name = "openssl-sys" 564 | version = "0.9.103" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 567 | dependencies = [ 568 | "cc", 569 | "libc", 570 | "pkg-config", 571 | "vcpkg", 572 | ] 573 | 574 | [[package]] 575 | name = "percent-encoding" 576 | version = "2.3.1" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 579 | 580 | [[package]] 581 | name = "pin-project-lite" 582 | version = "0.2.14" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 585 | 586 | [[package]] 587 | name = "pin-utils" 588 | version = "0.1.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 591 | 592 | [[package]] 593 | name = "pkg-config" 594 | version = "0.3.31" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 597 | 598 | [[package]] 599 | name = "portable-atomic" 600 | version = "1.9.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 603 | 604 | [[package]] 605 | name = "proc-macro2" 606 | version = "1.0.86" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 609 | dependencies = [ 610 | "unicode-ident", 611 | ] 612 | 613 | [[package]] 614 | name = "quote" 615 | version = "1.0.37" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 618 | dependencies = [ 619 | "proc-macro2", 620 | ] 621 | 622 | [[package]] 623 | name = "reqwest" 624 | version = "0.12.8" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" 627 | dependencies = [ 628 | "base64", 629 | "bytes", 630 | "encoding_rs", 631 | "futures-core", 632 | "futures-util", 633 | "h2", 634 | "http", 635 | "http-body", 636 | "http-body-util", 637 | "hyper", 638 | "hyper-rustls", 639 | "hyper-tls", 640 | "hyper-util", 641 | "ipnet", 642 | "js-sys", 643 | "log", 644 | "mime", 645 | "native-tls", 646 | "once_cell", 647 | "percent-encoding", 648 | "pin-project-lite", 649 | "rustls-pemfile", 650 | "serde", 651 | "serde_json", 652 | "serde_urlencoded", 653 | "sync_wrapper", 654 | "system-configuration", 655 | "tokio", 656 | "tokio-native-tls", 657 | "tokio-util", 658 | "tower-service", 659 | "url", 660 | "wasm-bindgen", 661 | "wasm-bindgen-futures", 662 | "wasm-streams", 663 | "web-sys", 664 | "windows-registry", 665 | ] 666 | 667 | [[package]] 668 | name = "ring" 669 | version = "0.17.8" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 672 | dependencies = [ 673 | "cc", 674 | "cfg-if", 675 | "getrandom", 676 | "libc", 677 | "spin", 678 | "untrusted", 679 | "windows-sys 0.52.0", 680 | ] 681 | 682 | [[package]] 683 | name = "rustc-demangle" 684 | version = "0.1.24" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 687 | 688 | [[package]] 689 | name = "rustix" 690 | version = "0.38.37" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 693 | dependencies = [ 694 | "bitflags", 695 | "errno", 696 | "libc", 697 | "linux-raw-sys", 698 | "windows-sys 0.52.0", 699 | ] 700 | 701 | [[package]] 702 | name = "rustls" 703 | version = "0.23.14" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" 706 | dependencies = [ 707 | "once_cell", 708 | "rustls-pki-types", 709 | "rustls-webpki", 710 | "subtle", 711 | "zeroize", 712 | ] 713 | 714 | [[package]] 715 | name = "rustls-pemfile" 716 | version = "2.2.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 719 | dependencies = [ 720 | "rustls-pki-types", 721 | ] 722 | 723 | [[package]] 724 | name = "rustls-pki-types" 725 | version = "1.9.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" 728 | 729 | [[package]] 730 | name = "rustls-webpki" 731 | version = "0.102.8" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 734 | dependencies = [ 735 | "ring", 736 | "rustls-pki-types", 737 | "untrusted", 738 | ] 739 | 740 | [[package]] 741 | name = "ryu" 742 | version = "1.0.18" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 745 | 746 | [[package]] 747 | name = "schannel" 748 | version = "0.1.24" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" 751 | dependencies = [ 752 | "windows-sys 0.59.0", 753 | ] 754 | 755 | [[package]] 756 | name = "security-framework" 757 | version = "2.11.1" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 760 | dependencies = [ 761 | "bitflags", 762 | "core-foundation", 763 | "core-foundation-sys", 764 | "libc", 765 | "security-framework-sys", 766 | ] 767 | 768 | [[package]] 769 | name = "security-framework-sys" 770 | version = "2.12.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 773 | dependencies = [ 774 | "core-foundation-sys", 775 | "libc", 776 | ] 777 | 778 | [[package]] 779 | name = "serde" 780 | version = "1.0.210" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 783 | dependencies = [ 784 | "serde_derive", 785 | ] 786 | 787 | [[package]] 788 | name = "serde_derive" 789 | version = "1.0.210" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 792 | dependencies = [ 793 | "proc-macro2", 794 | "quote", 795 | "syn", 796 | ] 797 | 798 | [[package]] 799 | name = "serde_json" 800 | version = "1.0.128" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 803 | dependencies = [ 804 | "itoa", 805 | "memchr", 806 | "ryu", 807 | "serde", 808 | ] 809 | 810 | [[package]] 811 | name = "serde_urlencoded" 812 | version = "0.7.1" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 815 | dependencies = [ 816 | "form_urlencoded", 817 | "itoa", 818 | "ryu", 819 | "serde", 820 | ] 821 | 822 | [[package]] 823 | name = "shlex" 824 | version = "1.3.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 827 | 828 | [[package]] 829 | name = "slab" 830 | version = "0.4.9" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 833 | dependencies = [ 834 | "autocfg", 835 | ] 836 | 837 | [[package]] 838 | name = "smallvec" 839 | version = "1.13.2" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 842 | 843 | [[package]] 844 | name = "socket2" 845 | version = "0.5.7" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 848 | dependencies = [ 849 | "libc", 850 | "windows-sys 0.52.0", 851 | ] 852 | 853 | [[package]] 854 | name = "spin" 855 | version = "0.9.8" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 858 | 859 | [[package]] 860 | name = "subtle" 861 | version = "2.6.1" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 864 | 865 | [[package]] 866 | name = "syn" 867 | version = "2.0.77" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 870 | dependencies = [ 871 | "proc-macro2", 872 | "quote", 873 | "unicode-ident", 874 | ] 875 | 876 | [[package]] 877 | name = "sync_wrapper" 878 | version = "1.0.1" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 881 | dependencies = [ 882 | "futures-core", 883 | ] 884 | 885 | [[package]] 886 | name = "system-configuration" 887 | version = "0.6.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 890 | dependencies = [ 891 | "bitflags", 892 | "core-foundation", 893 | "system-configuration-sys", 894 | ] 895 | 896 | [[package]] 897 | name = "system-configuration-sys" 898 | version = "0.6.0" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 901 | dependencies = [ 902 | "core-foundation-sys", 903 | "libc", 904 | ] 905 | 906 | [[package]] 907 | name = "tempfile" 908 | version = "3.13.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 911 | dependencies = [ 912 | "cfg-if", 913 | "fastrand", 914 | "once_cell", 915 | "rustix", 916 | "windows-sys 0.59.0", 917 | ] 918 | 919 | [[package]] 920 | name = "thiserror" 921 | version = "1.0.64" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 924 | dependencies = [ 925 | "thiserror-impl", 926 | ] 927 | 928 | [[package]] 929 | name = "thiserror-impl" 930 | version = "1.0.64" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 933 | dependencies = [ 934 | "proc-macro2", 935 | "quote", 936 | "syn", 937 | ] 938 | 939 | [[package]] 940 | name = "tinyvec" 941 | version = "1.8.0" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 944 | dependencies = [ 945 | "tinyvec_macros", 946 | ] 947 | 948 | [[package]] 949 | name = "tinyvec_macros" 950 | version = "0.1.1" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 953 | 954 | [[package]] 955 | name = "tokio" 956 | version = "1.40.0" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 959 | dependencies = [ 960 | "backtrace", 961 | "bytes", 962 | "libc", 963 | "mio", 964 | "pin-project-lite", 965 | "socket2", 966 | "windows-sys 0.52.0", 967 | ] 968 | 969 | [[package]] 970 | name = "tokio-native-tls" 971 | version = "0.3.1" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 974 | dependencies = [ 975 | "native-tls", 976 | "tokio", 977 | ] 978 | 979 | [[package]] 980 | name = "tokio-rustls" 981 | version = "0.26.0" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 984 | dependencies = [ 985 | "rustls", 986 | "rustls-pki-types", 987 | "tokio", 988 | ] 989 | 990 | [[package]] 991 | name = "tokio-util" 992 | version = "0.7.12" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 995 | dependencies = [ 996 | "bytes", 997 | "futures-core", 998 | "futures-sink", 999 | "pin-project-lite", 1000 | "tokio", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "tower-service" 1005 | version = "0.3.3" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1008 | 1009 | [[package]] 1010 | name = "tracing" 1011 | version = "0.1.40" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1014 | dependencies = [ 1015 | "pin-project-lite", 1016 | "tracing-attributes", 1017 | "tracing-core", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "tracing-attributes" 1022 | version = "0.1.27" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1025 | dependencies = [ 1026 | "proc-macro2", 1027 | "quote", 1028 | "syn", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "tracing-core" 1033 | version = "0.1.32" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1036 | dependencies = [ 1037 | "once_cell", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "try-lock" 1042 | version = "0.2.5" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1045 | 1046 | [[package]] 1047 | name = "unicode-bidi" 1048 | version = "0.3.17" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1051 | 1052 | [[package]] 1053 | name = "unicode-ident" 1054 | version = "1.0.13" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1057 | 1058 | [[package]] 1059 | name = "unicode-normalization" 1060 | version = "0.1.24" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1063 | dependencies = [ 1064 | "tinyvec", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "untrusted" 1069 | version = "0.9.0" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1072 | 1073 | [[package]] 1074 | name = "url" 1075 | version = "2.5.2" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1078 | dependencies = [ 1079 | "form_urlencoded", 1080 | "idna", 1081 | "percent-encoding", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "vcpkg" 1086 | version = "0.2.15" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1089 | 1090 | [[package]] 1091 | name = "want" 1092 | version = "0.3.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1095 | dependencies = [ 1096 | "try-lock", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "wasi" 1101 | version = "0.11.0+wasi-snapshot-preview1" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1104 | 1105 | [[package]] 1106 | name = "wasm-bindgen" 1107 | version = "0.2.93" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1110 | dependencies = [ 1111 | "cfg-if", 1112 | "once_cell", 1113 | "wasm-bindgen-macro", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "wasm-bindgen-backend" 1118 | version = "0.2.93" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1121 | dependencies = [ 1122 | "bumpalo", 1123 | "log", 1124 | "once_cell", 1125 | "proc-macro2", 1126 | "quote", 1127 | "syn", 1128 | "wasm-bindgen-shared", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "wasm-bindgen-futures" 1133 | version = "0.4.43" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 1136 | dependencies = [ 1137 | "cfg-if", 1138 | "js-sys", 1139 | "wasm-bindgen", 1140 | "web-sys", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "wasm-bindgen-macro" 1145 | version = "0.2.93" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1148 | dependencies = [ 1149 | "quote", 1150 | "wasm-bindgen-macro-support", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "wasm-bindgen-macro-support" 1155 | version = "0.2.93" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quote", 1161 | "syn", 1162 | "wasm-bindgen-backend", 1163 | "wasm-bindgen-shared", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "wasm-bindgen-shared" 1168 | version = "0.2.93" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1171 | 1172 | [[package]] 1173 | name = "wasm-streams" 1174 | version = "0.4.1" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" 1177 | dependencies = [ 1178 | "futures-util", 1179 | "js-sys", 1180 | "wasm-bindgen", 1181 | "wasm-bindgen-futures", 1182 | "web-sys", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "web-sys" 1187 | version = "0.3.70" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1190 | dependencies = [ 1191 | "js-sys", 1192 | "wasm-bindgen", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "windows-registry" 1197 | version = "0.2.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1200 | dependencies = [ 1201 | "windows-result", 1202 | "windows-strings", 1203 | "windows-targets", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "windows-result" 1208 | version = "0.2.0" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1211 | dependencies = [ 1212 | "windows-targets", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "windows-strings" 1217 | version = "0.1.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1220 | dependencies = [ 1221 | "windows-result", 1222 | "windows-targets", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "windows-sys" 1227 | version = "0.52.0" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1230 | dependencies = [ 1231 | "windows-targets", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "windows-sys" 1236 | version = "0.59.0" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1239 | dependencies = [ 1240 | "windows-targets", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "windows-targets" 1245 | version = "0.52.6" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1248 | dependencies = [ 1249 | "windows_aarch64_gnullvm", 1250 | "windows_aarch64_msvc", 1251 | "windows_i686_gnu", 1252 | "windows_i686_gnullvm", 1253 | "windows_i686_msvc", 1254 | "windows_x86_64_gnu", 1255 | "windows_x86_64_gnullvm", 1256 | "windows_x86_64_msvc", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "windows_aarch64_gnullvm" 1261 | version = "0.52.6" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1264 | 1265 | [[package]] 1266 | name = "windows_aarch64_msvc" 1267 | version = "0.52.6" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1270 | 1271 | [[package]] 1272 | name = "windows_i686_gnu" 1273 | version = "0.52.6" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1276 | 1277 | [[package]] 1278 | name = "windows_i686_gnullvm" 1279 | version = "0.52.6" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1282 | 1283 | [[package]] 1284 | name = "windows_i686_msvc" 1285 | version = "0.52.6" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1288 | 1289 | [[package]] 1290 | name = "windows_x86_64_gnu" 1291 | version = "0.52.6" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1294 | 1295 | [[package]] 1296 | name = "windows_x86_64_gnullvm" 1297 | version = "0.52.6" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1300 | 1301 | [[package]] 1302 | name = "windows_x86_64_msvc" 1303 | version = "0.52.6" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1306 | 1307 | [[package]] 1308 | name = "zeroize" 1309 | version = "1.8.1" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1312 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "connect-rpc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license-file = "LICENSE" 6 | description = "Connect RPC for Rust." 7 | repository = "https://github.com/lann/connect-rpc-rs" 8 | 9 | [features] 10 | default = ["reqwest"] 11 | reqwest = ["dep:reqwest"] 12 | 13 | [dependencies] 14 | base64 = "0.22" 15 | bytes = "1.7.2" 16 | form_urlencoded = "1.2.1" 17 | futures-util = "0.3.31" 18 | http = "1.1" 19 | http-body = "1.0.1" 20 | http-body-util = "0.1.2" 21 | percent-encoding = "2.3.1" 22 | serde = { version = "1.0.210", features = ["derive"] } 23 | serde_json = "1.0.128" 24 | thiserror = "1.0.64" 25 | tracing = "0.1.40" 26 | 27 | reqwest = { version = "0.12.8", features = ["stream"], optional = true } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect RPC for Rust 2 | 3 | [Connect](https://connectrpc.com/) for Rust. -------------------------------------------------------------------------------- /conformance/.gitignore: -------------------------------------------------------------------------------- 1 | /.work 2 | /target 3 | -------------------------------------------------------------------------------- /conformance/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.89" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 34 | 35 | [[package]] 36 | name = "atomic-waker" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 40 | 41 | [[package]] 42 | name = "autocfg" 43 | version = "1.4.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 46 | 47 | [[package]] 48 | name = "backtrace" 49 | version = "0.3.74" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 52 | dependencies = [ 53 | "addr2line", 54 | "cfg-if", 55 | "libc", 56 | "miniz_oxide", 57 | "object", 58 | "rustc-demangle", 59 | "windows-targets", 60 | ] 61 | 62 | [[package]] 63 | name = "base64" 64 | version = "0.22.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 67 | 68 | [[package]] 69 | name = "bitflags" 70 | version = "2.6.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 73 | 74 | [[package]] 75 | name = "bumpalo" 76 | version = "3.16.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 79 | 80 | [[package]] 81 | name = "bytes" 82 | version = "1.7.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 85 | 86 | [[package]] 87 | name = "cc" 88 | version = "1.1.24" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" 91 | dependencies = [ 92 | "shlex", 93 | ] 94 | 95 | [[package]] 96 | name = "cfg-if" 97 | version = "1.0.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 100 | 101 | [[package]] 102 | name = "connect-rpc" 103 | version = "0.1.0" 104 | dependencies = [ 105 | "base64", 106 | "bytes", 107 | "form_urlencoded", 108 | "futures-util", 109 | "http", 110 | "http-body", 111 | "http-body-util", 112 | "percent-encoding", 113 | "reqwest", 114 | "serde", 115 | "serde_json", 116 | "thiserror", 117 | "tracing", 118 | ] 119 | 120 | [[package]] 121 | name = "connect-rpc-conformance" 122 | version = "0.1.0" 123 | dependencies = [ 124 | "anyhow", 125 | "connect-rpc", 126 | "prost", 127 | "prost-types", 128 | "reqwest", 129 | "tokio", 130 | "tracing", 131 | "tracing-subscriber", 132 | ] 133 | 134 | [[package]] 135 | name = "core-foundation" 136 | version = "0.9.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 139 | dependencies = [ 140 | "core-foundation-sys", 141 | "libc", 142 | ] 143 | 144 | [[package]] 145 | name = "core-foundation-sys" 146 | version = "0.8.7" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 149 | 150 | [[package]] 151 | name = "either" 152 | version = "1.13.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 155 | 156 | [[package]] 157 | name = "encoding_rs" 158 | version = "0.8.34" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 161 | dependencies = [ 162 | "cfg-if", 163 | ] 164 | 165 | [[package]] 166 | name = "equivalent" 167 | version = "1.0.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 170 | 171 | [[package]] 172 | name = "errno" 173 | version = "0.3.9" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 176 | dependencies = [ 177 | "libc", 178 | "windows-sys 0.52.0", 179 | ] 180 | 181 | [[package]] 182 | name = "fastrand" 183 | version = "2.1.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 186 | 187 | [[package]] 188 | name = "fnv" 189 | version = "1.0.7" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 192 | 193 | [[package]] 194 | name = "foreign-types" 195 | version = "0.3.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 198 | dependencies = [ 199 | "foreign-types-shared", 200 | ] 201 | 202 | [[package]] 203 | name = "foreign-types-shared" 204 | version = "0.1.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 207 | 208 | [[package]] 209 | name = "form_urlencoded" 210 | version = "1.2.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 213 | dependencies = [ 214 | "percent-encoding", 215 | ] 216 | 217 | [[package]] 218 | name = "futures-channel" 219 | version = "0.3.30" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 222 | dependencies = [ 223 | "futures-core", 224 | ] 225 | 226 | [[package]] 227 | name = "futures-core" 228 | version = "0.3.31" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 231 | 232 | [[package]] 233 | name = "futures-io" 234 | version = "0.3.31" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 237 | 238 | [[package]] 239 | name = "futures-macro" 240 | version = "0.3.31" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 243 | dependencies = [ 244 | "proc-macro2", 245 | "quote", 246 | "syn", 247 | ] 248 | 249 | [[package]] 250 | name = "futures-sink" 251 | version = "0.3.31" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 254 | 255 | [[package]] 256 | name = "futures-task" 257 | version = "0.3.31" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 260 | 261 | [[package]] 262 | name = "futures-util" 263 | version = "0.3.31" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 266 | dependencies = [ 267 | "futures-core", 268 | "futures-io", 269 | "futures-macro", 270 | "futures-sink", 271 | "futures-task", 272 | "memchr", 273 | "pin-project-lite", 274 | "pin-utils", 275 | "slab", 276 | ] 277 | 278 | [[package]] 279 | name = "getrandom" 280 | version = "0.2.15" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 283 | dependencies = [ 284 | "cfg-if", 285 | "libc", 286 | "wasi", 287 | ] 288 | 289 | [[package]] 290 | name = "gimli" 291 | version = "0.31.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 294 | 295 | [[package]] 296 | name = "h2" 297 | version = "0.4.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 300 | dependencies = [ 301 | "atomic-waker", 302 | "bytes", 303 | "fnv", 304 | "futures-core", 305 | "futures-sink", 306 | "http", 307 | "indexmap", 308 | "slab", 309 | "tokio", 310 | "tokio-util", 311 | "tracing", 312 | ] 313 | 314 | [[package]] 315 | name = "hashbrown" 316 | version = "0.15.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 319 | 320 | [[package]] 321 | name = "hermit-abi" 322 | version = "0.3.9" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 325 | 326 | [[package]] 327 | name = "http" 328 | version = "1.1.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 331 | dependencies = [ 332 | "bytes", 333 | "fnv", 334 | "itoa", 335 | ] 336 | 337 | [[package]] 338 | name = "http-body" 339 | version = "1.0.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 342 | dependencies = [ 343 | "bytes", 344 | "http", 345 | ] 346 | 347 | [[package]] 348 | name = "http-body-util" 349 | version = "0.1.2" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 352 | dependencies = [ 353 | "bytes", 354 | "futures-util", 355 | "http", 356 | "http-body", 357 | "pin-project-lite", 358 | ] 359 | 360 | [[package]] 361 | name = "httparse" 362 | version = "1.9.5" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 365 | 366 | [[package]] 367 | name = "hyper" 368 | version = "1.4.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 371 | dependencies = [ 372 | "bytes", 373 | "futures-channel", 374 | "futures-util", 375 | "h2", 376 | "http", 377 | "http-body", 378 | "httparse", 379 | "itoa", 380 | "pin-project-lite", 381 | "smallvec", 382 | "tokio", 383 | "want", 384 | ] 385 | 386 | [[package]] 387 | name = "hyper-rustls" 388 | version = "0.27.3" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 391 | dependencies = [ 392 | "futures-util", 393 | "http", 394 | "hyper", 395 | "hyper-util", 396 | "rustls", 397 | "rustls-pki-types", 398 | "tokio", 399 | "tokio-rustls", 400 | "tower-service", 401 | ] 402 | 403 | [[package]] 404 | name = "hyper-tls" 405 | version = "0.6.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 408 | dependencies = [ 409 | "bytes", 410 | "http-body-util", 411 | "hyper", 412 | "hyper-util", 413 | "native-tls", 414 | "tokio", 415 | "tokio-native-tls", 416 | "tower-service", 417 | ] 418 | 419 | [[package]] 420 | name = "hyper-util" 421 | version = "0.1.9" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" 424 | dependencies = [ 425 | "bytes", 426 | "futures-channel", 427 | "futures-util", 428 | "http", 429 | "http-body", 430 | "hyper", 431 | "pin-project-lite", 432 | "socket2", 433 | "tokio", 434 | "tower-service", 435 | "tracing", 436 | ] 437 | 438 | [[package]] 439 | name = "idna" 440 | version = "0.5.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 443 | dependencies = [ 444 | "unicode-bidi", 445 | "unicode-normalization", 446 | ] 447 | 448 | [[package]] 449 | name = "indexmap" 450 | version = "2.6.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 453 | dependencies = [ 454 | "equivalent", 455 | "hashbrown", 456 | ] 457 | 458 | [[package]] 459 | name = "ipnet" 460 | version = "2.10.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 463 | 464 | [[package]] 465 | name = "itertools" 466 | version = "0.13.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 469 | dependencies = [ 470 | "either", 471 | ] 472 | 473 | [[package]] 474 | name = "itoa" 475 | version = "1.0.11" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 478 | 479 | [[package]] 480 | name = "js-sys" 481 | version = "0.3.70" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 484 | dependencies = [ 485 | "wasm-bindgen", 486 | ] 487 | 488 | [[package]] 489 | name = "lazy_static" 490 | version = "1.5.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 493 | 494 | [[package]] 495 | name = "libc" 496 | version = "0.2.159" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 499 | 500 | [[package]] 501 | name = "linux-raw-sys" 502 | version = "0.4.14" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 505 | 506 | [[package]] 507 | name = "lock_api" 508 | version = "0.4.12" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 511 | dependencies = [ 512 | "autocfg", 513 | "scopeguard", 514 | ] 515 | 516 | [[package]] 517 | name = "log" 518 | version = "0.4.22" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 521 | 522 | [[package]] 523 | name = "matchers" 524 | version = "0.1.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 527 | dependencies = [ 528 | "regex-automata 0.1.10", 529 | ] 530 | 531 | [[package]] 532 | name = "memchr" 533 | version = "2.7.4" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 536 | 537 | [[package]] 538 | name = "mime" 539 | version = "0.3.17" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 542 | 543 | [[package]] 544 | name = "miniz_oxide" 545 | version = "0.8.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 548 | dependencies = [ 549 | "adler2", 550 | ] 551 | 552 | [[package]] 553 | name = "mio" 554 | version = "1.0.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 557 | dependencies = [ 558 | "hermit-abi", 559 | "libc", 560 | "wasi", 561 | "windows-sys 0.52.0", 562 | ] 563 | 564 | [[package]] 565 | name = "native-tls" 566 | version = "0.2.12" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 569 | dependencies = [ 570 | "libc", 571 | "log", 572 | "openssl", 573 | "openssl-probe", 574 | "openssl-sys", 575 | "schannel", 576 | "security-framework", 577 | "security-framework-sys", 578 | "tempfile", 579 | ] 580 | 581 | [[package]] 582 | name = "nu-ansi-term" 583 | version = "0.46.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 586 | dependencies = [ 587 | "overload", 588 | "winapi", 589 | ] 590 | 591 | [[package]] 592 | name = "object" 593 | version = "0.36.5" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 596 | dependencies = [ 597 | "memchr", 598 | ] 599 | 600 | [[package]] 601 | name = "once_cell" 602 | version = "1.20.1" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" 605 | dependencies = [ 606 | "portable-atomic", 607 | ] 608 | 609 | [[package]] 610 | name = "openssl" 611 | version = "0.10.66" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 614 | dependencies = [ 615 | "bitflags", 616 | "cfg-if", 617 | "foreign-types", 618 | "libc", 619 | "once_cell", 620 | "openssl-macros", 621 | "openssl-sys", 622 | ] 623 | 624 | [[package]] 625 | name = "openssl-macros" 626 | version = "0.1.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 629 | dependencies = [ 630 | "proc-macro2", 631 | "quote", 632 | "syn", 633 | ] 634 | 635 | [[package]] 636 | name = "openssl-probe" 637 | version = "0.1.5" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 640 | 641 | [[package]] 642 | name = "openssl-sys" 643 | version = "0.9.103" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 646 | dependencies = [ 647 | "cc", 648 | "libc", 649 | "pkg-config", 650 | "vcpkg", 651 | ] 652 | 653 | [[package]] 654 | name = "overload" 655 | version = "0.1.1" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 658 | 659 | [[package]] 660 | name = "parking_lot" 661 | version = "0.12.3" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 664 | dependencies = [ 665 | "lock_api", 666 | "parking_lot_core", 667 | ] 668 | 669 | [[package]] 670 | name = "parking_lot_core" 671 | version = "0.9.10" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 674 | dependencies = [ 675 | "cfg-if", 676 | "libc", 677 | "redox_syscall", 678 | "smallvec", 679 | "windows-targets", 680 | ] 681 | 682 | [[package]] 683 | name = "percent-encoding" 684 | version = "2.3.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 687 | 688 | [[package]] 689 | name = "pin-project-lite" 690 | version = "0.2.14" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 693 | 694 | [[package]] 695 | name = "pin-utils" 696 | version = "0.1.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 699 | 700 | [[package]] 701 | name = "pkg-config" 702 | version = "0.3.31" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 705 | 706 | [[package]] 707 | name = "portable-atomic" 708 | version = "1.9.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 711 | 712 | [[package]] 713 | name = "proc-macro2" 714 | version = "1.0.86" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 717 | dependencies = [ 718 | "unicode-ident", 719 | ] 720 | 721 | [[package]] 722 | name = "prost" 723 | version = "0.13.3" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" 726 | dependencies = [ 727 | "bytes", 728 | "prost-derive", 729 | ] 730 | 731 | [[package]] 732 | name = "prost-derive" 733 | version = "0.13.3" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" 736 | dependencies = [ 737 | "anyhow", 738 | "itertools", 739 | "proc-macro2", 740 | "quote", 741 | "syn", 742 | ] 743 | 744 | [[package]] 745 | name = "prost-types" 746 | version = "0.13.3" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" 749 | dependencies = [ 750 | "prost", 751 | ] 752 | 753 | [[package]] 754 | name = "quote" 755 | version = "1.0.37" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 758 | dependencies = [ 759 | "proc-macro2", 760 | ] 761 | 762 | [[package]] 763 | name = "redox_syscall" 764 | version = "0.5.7" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 767 | dependencies = [ 768 | "bitflags", 769 | ] 770 | 771 | [[package]] 772 | name = "regex" 773 | version = "1.11.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 776 | dependencies = [ 777 | "aho-corasick", 778 | "memchr", 779 | "regex-automata 0.4.8", 780 | "regex-syntax 0.8.5", 781 | ] 782 | 783 | [[package]] 784 | name = "regex-automata" 785 | version = "0.1.10" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 788 | dependencies = [ 789 | "regex-syntax 0.6.29", 790 | ] 791 | 792 | [[package]] 793 | name = "regex-automata" 794 | version = "0.4.8" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 797 | dependencies = [ 798 | "aho-corasick", 799 | "memchr", 800 | "regex-syntax 0.8.5", 801 | ] 802 | 803 | [[package]] 804 | name = "regex-syntax" 805 | version = "0.6.29" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 808 | 809 | [[package]] 810 | name = "regex-syntax" 811 | version = "0.8.5" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 814 | 815 | [[package]] 816 | name = "reqwest" 817 | version = "0.12.8" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" 820 | dependencies = [ 821 | "base64", 822 | "bytes", 823 | "encoding_rs", 824 | "futures-core", 825 | "futures-util", 826 | "h2", 827 | "http", 828 | "http-body", 829 | "http-body-util", 830 | "hyper", 831 | "hyper-rustls", 832 | "hyper-tls", 833 | "hyper-util", 834 | "ipnet", 835 | "js-sys", 836 | "log", 837 | "mime", 838 | "native-tls", 839 | "once_cell", 840 | "percent-encoding", 841 | "pin-project-lite", 842 | "rustls-pemfile", 843 | "serde", 844 | "serde_json", 845 | "serde_urlencoded", 846 | "sync_wrapper", 847 | "system-configuration", 848 | "tokio", 849 | "tokio-native-tls", 850 | "tokio-util", 851 | "tower-service", 852 | "url", 853 | "wasm-bindgen", 854 | "wasm-bindgen-futures", 855 | "wasm-streams", 856 | "web-sys", 857 | "windows-registry", 858 | ] 859 | 860 | [[package]] 861 | name = "ring" 862 | version = "0.17.8" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 865 | dependencies = [ 866 | "cc", 867 | "cfg-if", 868 | "getrandom", 869 | "libc", 870 | "spin", 871 | "untrusted", 872 | "windows-sys 0.52.0", 873 | ] 874 | 875 | [[package]] 876 | name = "rustc-demangle" 877 | version = "0.1.24" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 880 | 881 | [[package]] 882 | name = "rustix" 883 | version = "0.38.37" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 886 | dependencies = [ 887 | "bitflags", 888 | "errno", 889 | "libc", 890 | "linux-raw-sys", 891 | "windows-sys 0.52.0", 892 | ] 893 | 894 | [[package]] 895 | name = "rustls" 896 | version = "0.23.14" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" 899 | dependencies = [ 900 | "once_cell", 901 | "rustls-pki-types", 902 | "rustls-webpki", 903 | "subtle", 904 | "zeroize", 905 | ] 906 | 907 | [[package]] 908 | name = "rustls-pemfile" 909 | version = "2.2.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 912 | dependencies = [ 913 | "rustls-pki-types", 914 | ] 915 | 916 | [[package]] 917 | name = "rustls-pki-types" 918 | version = "1.9.0" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" 921 | 922 | [[package]] 923 | name = "rustls-webpki" 924 | version = "0.102.8" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 927 | dependencies = [ 928 | "ring", 929 | "rustls-pki-types", 930 | "untrusted", 931 | ] 932 | 933 | [[package]] 934 | name = "ryu" 935 | version = "1.0.18" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 938 | 939 | [[package]] 940 | name = "schannel" 941 | version = "0.1.24" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" 944 | dependencies = [ 945 | "windows-sys 0.59.0", 946 | ] 947 | 948 | [[package]] 949 | name = "scopeguard" 950 | version = "1.2.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 953 | 954 | [[package]] 955 | name = "security-framework" 956 | version = "2.11.1" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 959 | dependencies = [ 960 | "bitflags", 961 | "core-foundation", 962 | "core-foundation-sys", 963 | "libc", 964 | "security-framework-sys", 965 | ] 966 | 967 | [[package]] 968 | name = "security-framework-sys" 969 | version = "2.12.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 972 | dependencies = [ 973 | "core-foundation-sys", 974 | "libc", 975 | ] 976 | 977 | [[package]] 978 | name = "serde" 979 | version = "1.0.210" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 982 | dependencies = [ 983 | "serde_derive", 984 | ] 985 | 986 | [[package]] 987 | name = "serde_derive" 988 | version = "1.0.210" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 991 | dependencies = [ 992 | "proc-macro2", 993 | "quote", 994 | "syn", 995 | ] 996 | 997 | [[package]] 998 | name = "serde_json" 999 | version = "1.0.128" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 1002 | dependencies = [ 1003 | "itoa", 1004 | "memchr", 1005 | "ryu", 1006 | "serde", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "serde_urlencoded" 1011 | version = "0.7.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1014 | dependencies = [ 1015 | "form_urlencoded", 1016 | "itoa", 1017 | "ryu", 1018 | "serde", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "sharded-slab" 1023 | version = "0.1.7" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1026 | dependencies = [ 1027 | "lazy_static", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "shlex" 1032 | version = "1.3.0" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1035 | 1036 | [[package]] 1037 | name = "signal-hook-registry" 1038 | version = "1.4.2" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1041 | dependencies = [ 1042 | "libc", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "slab" 1047 | version = "0.4.9" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1050 | dependencies = [ 1051 | "autocfg", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "smallvec" 1056 | version = "1.13.2" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1059 | 1060 | [[package]] 1061 | name = "socket2" 1062 | version = "0.5.7" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1065 | dependencies = [ 1066 | "libc", 1067 | "windows-sys 0.52.0", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "spin" 1072 | version = "0.9.8" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1075 | 1076 | [[package]] 1077 | name = "subtle" 1078 | version = "2.6.1" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1081 | 1082 | [[package]] 1083 | name = "syn" 1084 | version = "2.0.79" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 1087 | dependencies = [ 1088 | "proc-macro2", 1089 | "quote", 1090 | "unicode-ident", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "sync_wrapper" 1095 | version = "1.0.1" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1098 | dependencies = [ 1099 | "futures-core", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "system-configuration" 1104 | version = "0.6.1" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1107 | dependencies = [ 1108 | "bitflags", 1109 | "core-foundation", 1110 | "system-configuration-sys", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "system-configuration-sys" 1115 | version = "0.6.0" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1118 | dependencies = [ 1119 | "core-foundation-sys", 1120 | "libc", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "tempfile" 1125 | version = "3.13.0" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1128 | dependencies = [ 1129 | "cfg-if", 1130 | "fastrand", 1131 | "once_cell", 1132 | "rustix", 1133 | "windows-sys 0.59.0", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "thiserror" 1138 | version = "1.0.64" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1141 | dependencies = [ 1142 | "thiserror-impl", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "thiserror-impl" 1147 | version = "1.0.64" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1150 | dependencies = [ 1151 | "proc-macro2", 1152 | "quote", 1153 | "syn", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "thread_local" 1158 | version = "1.1.8" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1161 | dependencies = [ 1162 | "cfg-if", 1163 | "once_cell", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "tinyvec" 1168 | version = "1.8.0" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1171 | dependencies = [ 1172 | "tinyvec_macros", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "tinyvec_macros" 1177 | version = "0.1.1" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1180 | 1181 | [[package]] 1182 | name = "tokio" 1183 | version = "1.40.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 1186 | dependencies = [ 1187 | "backtrace", 1188 | "bytes", 1189 | "libc", 1190 | "mio", 1191 | "parking_lot", 1192 | "pin-project-lite", 1193 | "signal-hook-registry", 1194 | "socket2", 1195 | "tokio-macros", 1196 | "windows-sys 0.52.0", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "tokio-macros" 1201 | version = "2.4.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1204 | dependencies = [ 1205 | "proc-macro2", 1206 | "quote", 1207 | "syn", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "tokio-native-tls" 1212 | version = "0.3.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1215 | dependencies = [ 1216 | "native-tls", 1217 | "tokio", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "tokio-rustls" 1222 | version = "0.26.0" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1225 | dependencies = [ 1226 | "rustls", 1227 | "rustls-pki-types", 1228 | "tokio", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "tokio-util" 1233 | version = "0.7.12" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1236 | dependencies = [ 1237 | "bytes", 1238 | "futures-core", 1239 | "futures-sink", 1240 | "pin-project-lite", 1241 | "tokio", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "tower-service" 1246 | version = "0.3.3" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1249 | 1250 | [[package]] 1251 | name = "tracing" 1252 | version = "0.1.40" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1255 | dependencies = [ 1256 | "pin-project-lite", 1257 | "tracing-attributes", 1258 | "tracing-core", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "tracing-attributes" 1263 | version = "0.1.27" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1266 | dependencies = [ 1267 | "proc-macro2", 1268 | "quote", 1269 | "syn", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "tracing-core" 1274 | version = "0.1.32" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1277 | dependencies = [ 1278 | "once_cell", 1279 | "valuable", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "tracing-log" 1284 | version = "0.2.0" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1287 | dependencies = [ 1288 | "log", 1289 | "once_cell", 1290 | "tracing-core", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "tracing-subscriber" 1295 | version = "0.3.18" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1298 | dependencies = [ 1299 | "matchers", 1300 | "nu-ansi-term", 1301 | "once_cell", 1302 | "regex", 1303 | "sharded-slab", 1304 | "smallvec", 1305 | "thread_local", 1306 | "tracing", 1307 | "tracing-core", 1308 | "tracing-log", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "try-lock" 1313 | version = "0.2.5" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1316 | 1317 | [[package]] 1318 | name = "unicode-bidi" 1319 | version = "0.3.17" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1322 | 1323 | [[package]] 1324 | name = "unicode-ident" 1325 | version = "1.0.13" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1328 | 1329 | [[package]] 1330 | name = "unicode-normalization" 1331 | version = "0.1.24" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1334 | dependencies = [ 1335 | "tinyvec", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "untrusted" 1340 | version = "0.9.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1343 | 1344 | [[package]] 1345 | name = "url" 1346 | version = "2.5.2" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1349 | dependencies = [ 1350 | "form_urlencoded", 1351 | "idna", 1352 | "percent-encoding", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "valuable" 1357 | version = "0.1.0" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1360 | 1361 | [[package]] 1362 | name = "vcpkg" 1363 | version = "0.2.15" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1366 | 1367 | [[package]] 1368 | name = "want" 1369 | version = "0.3.1" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1372 | dependencies = [ 1373 | "try-lock", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "wasi" 1378 | version = "0.11.0+wasi-snapshot-preview1" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1381 | 1382 | [[package]] 1383 | name = "wasm-bindgen" 1384 | version = "0.2.93" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1387 | dependencies = [ 1388 | "cfg-if", 1389 | "once_cell", 1390 | "wasm-bindgen-macro", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "wasm-bindgen-backend" 1395 | version = "0.2.93" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1398 | dependencies = [ 1399 | "bumpalo", 1400 | "log", 1401 | "once_cell", 1402 | "proc-macro2", 1403 | "quote", 1404 | "syn", 1405 | "wasm-bindgen-shared", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "wasm-bindgen-futures" 1410 | version = "0.4.43" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 1413 | dependencies = [ 1414 | "cfg-if", 1415 | "js-sys", 1416 | "wasm-bindgen", 1417 | "web-sys", 1418 | ] 1419 | 1420 | [[package]] 1421 | name = "wasm-bindgen-macro" 1422 | version = "0.2.93" 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" 1424 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1425 | dependencies = [ 1426 | "quote", 1427 | "wasm-bindgen-macro-support", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "wasm-bindgen-macro-support" 1432 | version = "0.2.93" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1435 | dependencies = [ 1436 | "proc-macro2", 1437 | "quote", 1438 | "syn", 1439 | "wasm-bindgen-backend", 1440 | "wasm-bindgen-shared", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "wasm-bindgen-shared" 1445 | version = "0.2.93" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1448 | 1449 | [[package]] 1450 | name = "wasm-streams" 1451 | version = "0.4.1" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" 1454 | dependencies = [ 1455 | "futures-util", 1456 | "js-sys", 1457 | "wasm-bindgen", 1458 | "wasm-bindgen-futures", 1459 | "web-sys", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "web-sys" 1464 | version = "0.3.70" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1467 | dependencies = [ 1468 | "js-sys", 1469 | "wasm-bindgen", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "winapi" 1474 | version = "0.3.9" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1477 | dependencies = [ 1478 | "winapi-i686-pc-windows-gnu", 1479 | "winapi-x86_64-pc-windows-gnu", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "winapi-i686-pc-windows-gnu" 1484 | version = "0.4.0" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1487 | 1488 | [[package]] 1489 | name = "winapi-x86_64-pc-windows-gnu" 1490 | version = "0.4.0" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1493 | 1494 | [[package]] 1495 | name = "windows-registry" 1496 | version = "0.2.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1499 | dependencies = [ 1500 | "windows-result", 1501 | "windows-strings", 1502 | "windows-targets", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "windows-result" 1507 | version = "0.2.0" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1510 | dependencies = [ 1511 | "windows-targets", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "windows-strings" 1516 | version = "0.1.0" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1519 | dependencies = [ 1520 | "windows-result", 1521 | "windows-targets", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "windows-sys" 1526 | version = "0.52.0" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1529 | dependencies = [ 1530 | "windows-targets", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "windows-sys" 1535 | version = "0.59.0" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1538 | dependencies = [ 1539 | "windows-targets", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "windows-targets" 1544 | version = "0.52.6" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1547 | dependencies = [ 1548 | "windows_aarch64_gnullvm", 1549 | "windows_aarch64_msvc", 1550 | "windows_i686_gnu", 1551 | "windows_i686_gnullvm", 1552 | "windows_i686_msvc", 1553 | "windows_x86_64_gnu", 1554 | "windows_x86_64_gnullvm", 1555 | "windows_x86_64_msvc", 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "windows_aarch64_gnullvm" 1560 | version = "0.52.6" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1563 | 1564 | [[package]] 1565 | name = "windows_aarch64_msvc" 1566 | version = "0.52.6" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1569 | 1570 | [[package]] 1571 | name = "windows_i686_gnu" 1572 | version = "0.52.6" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1575 | 1576 | [[package]] 1577 | name = "windows_i686_gnullvm" 1578 | version = "0.52.6" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1581 | 1582 | [[package]] 1583 | name = "windows_i686_msvc" 1584 | version = "0.52.6" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1587 | 1588 | [[package]] 1589 | name = "windows_x86_64_gnu" 1590 | version = "0.52.6" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1593 | 1594 | [[package]] 1595 | name = "windows_x86_64_gnullvm" 1596 | version = "0.52.6" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1599 | 1600 | [[package]] 1601 | name = "windows_x86_64_msvc" 1602 | version = "0.52.6" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1605 | 1606 | [[package]] 1607 | name = "zeroize" 1608 | version = "1.8.1" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1611 | -------------------------------------------------------------------------------- /conformance/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "connect-rpc-conformance" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.89" 8 | connect-rpc = { path = ".." } 9 | prost = "0.13.3" 10 | prost-types = "0.13.3" 11 | reqwest = { version = "0.12.8", features = ["json", "stream"] } 12 | tokio = { version = "1.40.0", features = ["full"] } 13 | tracing = "0.1.40" 14 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 15 | -------------------------------------------------------------------------------- /conformance/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | managed: 3 | enabled: true 4 | plugins: 5 | - remote: buf.build/community/neoeinstein-prost:v0.4.0 6 | out: gen -------------------------------------------------------------------------------- /conformance/conformance.yaml: -------------------------------------------------------------------------------- 1 | features: 2 | versions: 3 | - HTTP_VERSION_1 4 | - HTTP_VERSION_2 5 | protocols: 6 | - PROTOCOL_CONNECT 7 | codecs: 8 | - CODEC_PROTO 9 | # TODO: CODEC_JSON 10 | compressions: 11 | - COMPRESSION_IDENTITY 12 | stream_types: 13 | - STREAM_TYPE_UNARY 14 | # TODO: streaming types 15 | supportsTls: false 16 | supportsMessageReceiveLimit: false -------------------------------------------------------------------------------- /conformance/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | case "${OSTYPE}" in 6 | darwin*) os='Darwin' ;; 7 | linux*) os='Linux' ;; 8 | msys*) os='Windows' ;; 9 | *) echo "unknown OSTYPE ${OSTYPE}"; exit 1 ;; 10 | esac 11 | arch="$(uname -m)" 12 | ver="1.0.4" 13 | conformance_tgz="connectconformance-v${ver}-${os}-${arch}.tar.gz" 14 | url="https://github.com/connectrpc/conformance/releases/download/v${ver}/${conformance_tgz}" 15 | 16 | cd "$(dirname "${BASH_SOURCE[0]}")" 17 | 18 | conformance=".work/conformance-${ver}" 19 | 20 | [[ -e "${conformance}" ]] || ( 21 | echo "Downloading ${url}" 22 | mkdir .work || true 23 | cd .work 24 | wget -nv "${url}" 25 | tar xf "${conformance_tgz}" 26 | mv connectconformance "../${conformance}" 27 | ) 28 | 29 | cargo build 30 | $conformance "$@" --conf conformance.yaml --mode client -- target/debug/connect-rpc-conformance 31 | -------------------------------------------------------------------------------- /conformance/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::{ErrorKind, Write}, 4 | }; 5 | 6 | use anyhow::{bail, ensure}; 7 | use connect_rpc::{ 8 | metadata::Metadata, 9 | request::builder::RequestBuilder, 10 | reqwest::ReqwestClientExt, 11 | response::{ 12 | error::{ConnectCode, ConnectError}, 13 | ConnectResponse, 14 | }, 15 | }; 16 | use prost::Message; 17 | use tokio::{io::AsyncReadExt, task::JoinSet}; 18 | use tracing_subscriber::{fmt::format, prelude::*, EnvFilter}; 19 | 20 | mod proto { 21 | include!("../gen/connectrpc.conformance.v1.rs"); 22 | } 23 | use proto::{ 24 | client_compat_response::Result as ClientCompatResult, ClientCompatRequest, 25 | ClientCompatResponse, ClientErrorResult, ClientResponseResult, Error as ResponseError, Header, 26 | HttpVersion, 27 | }; 28 | 29 | #[tokio::main] 30 | async fn main() -> anyhow::Result<()> { 31 | tracing_subscriber::registry() 32 | .with( 33 | tracing_subscriber::fmt::layer() 34 | .with_writer(std::io::stderr) 35 | .event_format(format().compact()), 36 | ) 37 | .with(EnvFilter::from_default_env()) 38 | .init(); 39 | 40 | let mut tasks = JoinSet::new(); 41 | while let Some(req) = read_request().await? { 42 | tasks.spawn(handle_client_test(req)); 43 | // TODO configure parallelism 44 | while tasks.len() > 16 { 45 | tasks.join_next().await; 46 | } 47 | } 48 | tasks.join_all().await; 49 | Ok(()) 50 | } 51 | 52 | async fn handle_client_test(test: ClientCompatRequest) { 53 | let test_name = test.test_name.clone(); 54 | tracing::debug!(test_name, "Running client test"); 55 | 56 | let result = match run_client_test(test).await { 57 | Ok(response) => { 58 | tracing::debug!(?response, "Sending response"); 59 | ClientCompatResult::Response(response) 60 | } 61 | Err(err) => ClientCompatResult::Error(ClientErrorResult { 62 | message: err.to_string(), 63 | }), 64 | }; 65 | if let Err(err) = write_response(ClientCompatResponse { 66 | test_name, 67 | result: Some(result), 68 | }) { 69 | panic!("Error writing response: {err:?}"); 70 | } 71 | } 72 | 73 | async fn run_client_test(test: ClientCompatRequest) -> anyhow::Result { 74 | tracing::trace!(?test); 75 | 76 | // Assert supported test features 77 | ensure!(test.protocol() == proto::Protocol::Connect); 78 | ensure!(test.codec() == proto::Codec::Proto); 79 | ensure!(test.compression() == proto::Compression::Identity); 80 | ensure!(test.server_tls_cert.is_empty()); 81 | ensure!(test.client_tls_creds.is_none()); 82 | 83 | let client = { 84 | let builder = reqwest::Client::builder(); 85 | let builder = match test.http_version() { 86 | HttpVersion::Unspecified => builder, 87 | HttpVersion::HttpVersion1 => builder.http1_only(), 88 | HttpVersion::HttpVersion2 => builder.http2_prior_knowledge(), 89 | HttpVersion::HttpVersion3 => bail!("HTTP3 not supported"), 90 | }; 91 | builder.build()? 92 | }; 93 | 94 | let resp_result = { 95 | let mut builder = RequestBuilder::default() 96 | .scheme("http")? 97 | .authority(format!("{}:{}", test.host, test.port))? 98 | .protobuf_rpc(test.service(), test.method())? 99 | .message_codec("proto")?; 100 | 101 | if let Some(timeout_ms) = test.timeout_ms { 102 | builder = builder.timeout_ms(timeout_ms.into())?; 103 | } 104 | 105 | for header in test.request_headers { 106 | for value in header.value { 107 | builder = builder.ascii_metadata(&header.name, value)?; 108 | } 109 | } 110 | 111 | let msg = &test.request_messages[0].value; 112 | tracing::trace!(msg = %msg.escape_ascii()); 113 | if test.use_get_http_method { 114 | client.execute_unary_get(builder.unary_get(msg)?).await 115 | } else { 116 | client.execute_unary(builder.unary(msg.clone())?).await 117 | } 118 | }; 119 | tracing::trace!(?resp_result); 120 | 121 | if test.cancel.is_some() { 122 | return Ok(ConnectCode::Canceled.into()); 123 | } 124 | 125 | match resp_result { 126 | Ok(resp) => { 127 | let resp_msg = proto::UnaryResponse::decode(resp.body().as_ref())?; 128 | let (response_headers, response_trailers) = headers_and_trailers(resp.metadata()); 129 | let payloads = vec![resp_msg.payload.unwrap_or_default()]; 130 | Ok(ClientResponseResult { 131 | response_headers, 132 | response_trailers, 133 | payloads, 134 | ..Default::default() 135 | }) 136 | } 137 | Err(err) => { 138 | let connect_error = ConnectError::from(err); 139 | let (response_headers, response_trailers) = 140 | headers_and_trailers(connect_error.metadata()); 141 | let code = connect_error.code(); 142 | let details = connect_error 143 | .details 144 | .into_iter() 145 | .map(|detail| { 146 | Ok(prost_types::Any { 147 | type_url: detail.type_url(), 148 | value: detail.value()?, 149 | }) 150 | }) 151 | .collect::>()?; 152 | Ok(ClientResponseResult { 153 | response_headers, 154 | response_trailers, 155 | error: Some(ResponseError { 156 | code: proto::Code::from(code) as i32, 157 | message: Some(connect_error.message), 158 | details, 159 | }), 160 | ..Default::default() 161 | }) 162 | } 163 | } 164 | } 165 | 166 | async fn read_request() -> anyhow::Result> { 167 | let len = match tokio::io::stdin().read_u32().await { 168 | Ok(len) => len, 169 | Err(err) if err.kind() == ErrorKind::UnexpectedEof => return Ok(None), 170 | err @ Err(_) => err?, 171 | }; 172 | let mut buf = vec![0; len.try_into().unwrap()]; 173 | tokio::io::stdin().read_exact(&mut buf).await?; 174 | let config = T::decode(&buf[..])?; 175 | Ok(Some(config)) 176 | } 177 | 178 | fn write_response(resp: impl Message) -> anyhow::Result<()> { 179 | let buf = resp.encode_to_vec(); 180 | let len: u32 = buf.len().try_into()?; 181 | let mut stdout = std::io::stdout().lock(); 182 | stdout.write_all(&len.to_be_bytes())?; 183 | stdout.write_all(&buf)?; 184 | stdout.flush()?; 185 | Ok(()) 186 | } 187 | 188 | fn headers_and_trailers(metadata: &impl Metadata) -> (Vec
, Vec
) { 189 | let mut headers: HashMap<&str, Header> = HashMap::new(); 190 | let mut trailers: HashMap<&str, Header> = HashMap::new(); 191 | for (key, val) in metadata.iter_ascii() { 192 | let map = if key.ends_with("-trailer") { 193 | &mut trailers 194 | } else { 195 | &mut headers 196 | }; 197 | map.entry(key) 198 | .or_insert_with(|| Header { 199 | name: key.to_string(), 200 | ..Default::default() 201 | }) 202 | .value 203 | .push(val.to_string()); 204 | } 205 | ( 206 | headers.into_values().collect(), 207 | trailers.into_values().collect(), 208 | ) 209 | } 210 | 211 | impl From for ClientResponseResult { 212 | fn from(code: ConnectCode) -> Self { 213 | Self { 214 | error: Some(ResponseError { 215 | code: proto::Code::from(code) as i32, 216 | ..Default::default() 217 | }), 218 | ..Default::default() 219 | } 220 | } 221 | } 222 | 223 | impl From for proto::Code { 224 | fn from(code: ConnectCode) -> Self { 225 | match code { 226 | ConnectCode::Ok => Self::Unspecified, 227 | ConnectCode::Canceled => Self::Canceled, 228 | ConnectCode::Unknown => Self::Unknown, 229 | ConnectCode::InvalidArgument => Self::InvalidArgument, 230 | ConnectCode::DeadlineExceeded => Self::DeadlineExceeded, 231 | ConnectCode::NotFound => Self::NotFound, 232 | ConnectCode::AlreadyExists => Self::AlreadyExists, 233 | ConnectCode::PermissionDenied => Self::PermissionDenied, 234 | ConnectCode::ResourceExhausted => Self::ResourceExhausted, 235 | ConnectCode::FailedPrecondition => Self::FailedPrecondition, 236 | ConnectCode::Aborted => Self::Aborted, 237 | ConnectCode::OutOfRange => Self::OutOfRange, 238 | ConnectCode::Unimplemented => Self::Unimplemented, 239 | ConnectCode::Internal => Self::Internal, 240 | ConnectCode::Unavailable => Self::Unavailable, 241 | ConnectCode::DataLoss => Self::DataLoss, 242 | ConnectCode::Unauthenticated => Self::Unauthenticated, 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use base64::prelude::{Engine, BASE64_STANDARD_NO_PAD}; 2 | use http::{header, HeaderMap, HeaderName, HeaderValue}; 3 | 4 | use crate::Error; 5 | 6 | pub const CONNECT_PROTOCOL_VERSION: HeaderName = 7 | HeaderName::from_static("connect-protocol-version"); 8 | pub const PROTOCOL_VERSION_1: HeaderValue = HeaderValue::from_static("1"); 9 | 10 | pub const CONNECT_TIMEOUT_MS: HeaderName = HeaderName::from_static("connect-timeout-ms"); 11 | 12 | pub const CONNECT_CONTENT_ENCODING: HeaderName = 13 | HeaderName::from_static("connect-content-encoding"); 14 | pub const CONNECT_ACCEPT_ENCODING: HeaderName = HeaderName::from_static("connect-accept-encoding"); 15 | pub const CONTENT_ENCODING_IDENTITY: HeaderValue = HeaderValue::from_static("identity"); 16 | 17 | pub const CONTENT_TYPE_PREFIX: &str = "application/"; 18 | pub const STREAMING_CONTENT_TYPE_PREFIX: &str = "application/connect+"; 19 | pub const STREAMING_CONTENT_SUBTYPE_PREFIX: &str = "connect+"; 20 | 21 | pub fn base64_encode(input: impl AsRef<[u8]>) -> String { 22 | BASE64_STANDARD_NO_PAD.encode(input) 23 | } 24 | 25 | pub fn base64_decode(b64: impl AsRef<[u8]>) -> Result, Error> { 26 | Ok(BASE64_STANDARD_NO_PAD.decode(b64)?) 27 | } 28 | 29 | pub fn is_valid_http_token(s: &str) -> bool { 30 | // https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#tokens 31 | !s.is_empty() 32 | && s.chars() 33 | .all(|c| c.is_ascii_alphanumeric() || "!#$%&'*+-.^_`|~".contains(c)) 34 | } 35 | 36 | pub fn unary_message_codec(headers: &HeaderMap) -> Result<&str, Error> { 37 | let codec = content_type(headers)? 38 | .strip_prefix(CONTENT_TYPE_PREFIX) 39 | .ok_or(Error::invalid_request( 40 | "content-type must start with 'application/'", 41 | ))?; 42 | if codec.starts_with(STREAMING_CONTENT_SUBTYPE_PREFIX) { 43 | return Err(Error::invalid_request( 44 | "unary request with streaming content-type", 45 | )); 46 | } 47 | Ok(codec) 48 | } 49 | 50 | pub fn streaming_message_codec(headers: &HeaderMap) -> Result<&str, Error> { 51 | content_type(headers)? 52 | .strip_prefix(STREAMING_CONTENT_SUBTYPE_PREFIX) 53 | .ok_or(Error::invalid_request( 54 | "streaming content-type must start with 'application/connect+'", 55 | )) 56 | } 57 | 58 | fn content_type(headers: &HeaderMap) -> Result<&str, Error> { 59 | headers 60 | .get(header::CONTENT_TYPE) 61 | .ok_or(Error::invalid_request("missing content-type"))? 62 | .to_str() 63 | .map_err(|_| Error::invalid_request("invalid content-type")) 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use response::error::ConnectError; 2 | 3 | pub(crate) mod common; 4 | pub mod metadata; 5 | pub mod request; 6 | pub mod response; 7 | pub mod stream; 8 | 9 | #[cfg(feature = "reqwest")] 10 | pub mod reqwest; 11 | 12 | pub(crate) type BoxError = Box; 13 | 14 | #[non_exhaustive] 15 | #[derive(Debug, thiserror::Error)] 16 | pub enum Error { 17 | #[error("base64 decode error: {0}")] 18 | Base64DecodeError(#[from] base64::DecodeError), 19 | #[error("body error: {0}")] 20 | BodyError(#[source] BoxError), 21 | #[error("{0}")] 22 | ConnectError(ConnectError), 23 | #[error("invalid request: {0}")] 24 | InvalidRequest(String), 25 | #[error("invalid response: {0}")] 26 | InvalidResponse(String), 27 | #[error("invalid metadata: {0}")] 28 | InvalidMetadata(&'static str), 29 | #[error("invalid header name: {0}")] 30 | InvalidHeaderName(#[from] http::header::InvalidHeaderName), 31 | #[error("invalid header value: {0}")] 32 | InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), 33 | #[error("invalid URI: {0}")] 34 | InvalidUri(#[from] http::uri::InvalidUri), 35 | #[error("invalid URI: {0}")] 36 | InvalidUriParts(#[from] http::uri::InvalidUriParts), 37 | #[error("unacceptable encoding {0:?}")] 38 | UnacceptableEncoding(String), 39 | #[error("unexpected message codec {0:?}")] 40 | UnexpectedMessageCodec(String), 41 | 42 | #[cfg(feature = "reqwest")] 43 | #[error("reqwest error: {0}")] 44 | ReqwestError(#[source] ::reqwest::Error), 45 | } 46 | 47 | impl Error { 48 | pub(crate) fn body(err: impl Into) -> Self { 49 | Self::BodyError(err.into()) 50 | } 51 | 52 | pub(crate) fn invalid_request(msg: impl std::fmt::Display) -> Self { 53 | Self::InvalidRequest(msg.to_string()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/metadata.rs: -------------------------------------------------------------------------------- 1 | use http::{header::AsHeaderName, HeaderMap, HeaderName, HeaderValue}; 2 | 3 | use crate::{ 4 | common::{base64_decode, base64_encode}, 5 | Error, 6 | }; 7 | 8 | const BIN_SUFFIX: &str = "-bin"; 9 | const TRAILER_PREFIX: &str = "trailer-"; 10 | 11 | pub trait Metadata { 12 | fn get_ascii(&self, key: impl AsHeaderName + AsRef) -> Option<&str>; 13 | 14 | fn get_binary(&self, key: impl AsHeaderName + AsRef) -> Option>; 15 | 16 | fn get_all_ascii(&self, key: impl AsHeaderName + AsRef) -> impl Iterator; 17 | 18 | fn get_all_binary( 19 | &self, 20 | key: impl AsHeaderName + AsRef, 21 | ) -> impl Iterator> + '_; 22 | 23 | fn iter_ascii(&self) -> impl Iterator; 24 | 25 | fn iter_binary(&self) -> impl Iterator)>; 26 | 27 | fn insert_ascii( 28 | &mut self, 29 | key: impl TryInto>, 30 | val: impl Into, 31 | ) -> Result<(), Error>; 32 | 33 | fn insert_binary( 34 | &mut self, 35 | key: impl TryInto>, 36 | val: impl AsRef<[u8]>, 37 | ) -> Result<(), Error>; 38 | 39 | fn append_ascii( 40 | &mut self, 41 | key: impl TryInto>, 42 | val: impl Into, 43 | ) -> Result<(), Error>; 44 | 45 | fn append_binary( 46 | &mut self, 47 | key: impl TryInto>, 48 | val: impl AsRef<[u8]>, 49 | ) -> Result<(), Error>; 50 | } 51 | 52 | impl Metadata for HeaderMap { 53 | fn get_ascii(&self, key: impl AsHeaderName + AsRef) -> Option<&str> { 54 | if key.as_ref().ends_with(BIN_SUFFIX) { 55 | return None; 56 | } 57 | get_maybe_trailer(self, key)?.to_str().ok() 58 | } 59 | 60 | fn get_binary(&self, key: impl AsHeaderName + AsRef) -> Option> { 61 | if !key.as_ref().ends_with(BIN_SUFFIX) { 62 | return None; 63 | } 64 | let b64 = get_maybe_trailer(self, key)?; 65 | base64_decode(b64).ok() 66 | } 67 | 68 | fn get_all_ascii(&self, key: impl AsHeaderName + AsRef) -> impl Iterator { 69 | let override_empty = key.as_ref().ends_with(BIN_SUFFIX); 70 | get_all_maybe_trailer(self, key, override_empty).filter_map(|val| val.to_str().ok()) 71 | } 72 | 73 | fn get_all_binary( 74 | &self, 75 | key: impl AsHeaderName + AsRef, 76 | ) -> impl Iterator> + '_ { 77 | let override_empty = !key.as_ref().ends_with(BIN_SUFFIX); 78 | get_all_maybe_trailer(self, key, override_empty).filter_map(|val| base64_decode(val).ok()) 79 | } 80 | 81 | fn iter_ascii(&self) -> impl Iterator { 82 | self.iter().filter_map(|(key, val)| { 83 | let key = key.as_str(); 84 | if key.ends_with(BIN_SUFFIX) { 85 | return None; 86 | } 87 | let key = key.strip_prefix(TRAILER_PREFIX).unwrap_or(key); 88 | Some((key, val.to_str().ok()?)) 89 | }) 90 | } 91 | 92 | fn iter_binary(&self) -> impl Iterator)> { 93 | self.iter().filter_map(|(key, val)| { 94 | let key = key.as_str(); 95 | if !key.ends_with(BIN_SUFFIX) { 96 | return None; 97 | } 98 | let key = key.strip_prefix(TRAILER_PREFIX).unwrap_or(key); 99 | Some((key, base64_decode(val).ok()?)) 100 | }) 101 | } 102 | 103 | fn insert_ascii( 104 | &mut self, 105 | key: impl TryInto>, 106 | val: impl Into, 107 | ) -> Result<(), Error> { 108 | self.insert(ascii_key(key)?, ascii_value(val)?); 109 | Ok(()) 110 | } 111 | 112 | fn insert_binary( 113 | &mut self, 114 | key: impl TryInto>, 115 | val: impl AsRef<[u8]>, 116 | ) -> Result<(), Error> { 117 | self.insert(binary_key(key)?, binary_value(val)); 118 | Ok(()) 119 | } 120 | 121 | fn append_ascii( 122 | &mut self, 123 | key: impl TryInto>, 124 | val: impl Into, 125 | ) -> Result<(), Error> { 126 | self.append(ascii_key(key)?, ascii_value(val)?); 127 | Ok(()) 128 | } 129 | 130 | fn append_binary( 131 | &mut self, 132 | key: impl TryInto>, 133 | val: impl AsRef<[u8]>, 134 | ) -> Result<(), Error> { 135 | self.append(binary_key(key)?, binary_value(val)); 136 | Ok(()) 137 | } 138 | } 139 | 140 | fn get_maybe_trailer( 141 | headers: &HeaderMap, 142 | key: impl AsHeaderName + AsRef, 143 | ) -> Option<&HeaderValue> { 144 | let trailer_key = format!("{TRAILER_PREFIX}{}", key.as_ref()); 145 | headers.get(key).or_else(|| headers.get(trailer_key)) 146 | } 147 | 148 | fn get_all_maybe_trailer( 149 | headers: &HeaderMap, 150 | key: impl AsHeaderName + AsRef, 151 | override_empty: bool, 152 | ) -> impl Iterator + '_ { 153 | if override_empty { 154 | Box::new(std::iter::empty()) as Box> 155 | } else { 156 | let trailer_key = format!("{TRAILER_PREFIX}{}", key.as_ref()); 157 | Box::new( 158 | headers 159 | .get_all(key) 160 | .into_iter() 161 | .chain(headers.get_all(trailer_key)), 162 | ) 163 | } 164 | } 165 | 166 | fn ascii_key(key: impl TryInto>) -> Result { 167 | let key = key.try_into().map_err(Into::into)?; 168 | if key.as_str().ends_with(BIN_SUFFIX) { 169 | return Err(Error::InvalidMetadata( 170 | "ASCII metadata keys may not end with '-bin'", 171 | )); 172 | } 173 | Ok(key) 174 | } 175 | 176 | fn binary_key(key: impl TryInto>) -> Result { 177 | let key = key.try_into().map_err(Into::into)?; 178 | if !key.as_str().ends_with(BIN_SUFFIX) { 179 | return Err(Error::InvalidMetadata( 180 | "binary metadata keys must end with '-bin'", 181 | )); 182 | } 183 | Ok(key) 184 | } 185 | 186 | fn ascii_value(value: impl Into) -> Result { 187 | let value = value.into(); 188 | // ASCII-Value → 1*( %x20-%x7E ) ; space & printable ASCII 189 | if !value.chars().all(|c| c.is_ascii_graphic() || c == ' ') { 190 | return Err(Error::InvalidMetadata( 191 | "ASCII metadata values may only contain printable characters and spaces", 192 | )); 193 | } 194 | Ok(value.try_into()?) 195 | } 196 | 197 | fn binary_value(value: impl AsRef<[u8]>) -> HeaderValue { 198 | base64_encode(value).try_into().unwrap() 199 | } 200 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, time::Duration}; 2 | 3 | use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; 4 | use http::{ 5 | header, 6 | uri::{Authority, Scheme}, 7 | HeaderMap, Method, Uri, 8 | }; 9 | 10 | use crate::{ 11 | common::{ 12 | streaming_message_codec, unary_message_codec, CONNECT_ACCEPT_ENCODING, 13 | CONNECT_CONTENT_ENCODING, CONNECT_PROTOCOL_VERSION, CONNECT_TIMEOUT_MS, PROTOCOL_VERSION_1, 14 | STREAMING_CONTENT_TYPE_PREFIX, 15 | }, 16 | metadata::Metadata, 17 | Error, 18 | }; 19 | 20 | pub mod builder; 21 | 22 | /// A Connect request. 23 | pub trait ConnectRequest { 24 | /// Returns the connect protocol version. 25 | fn connect_protocol_version(&self) -> Option<&str>; 26 | 27 | /// Returns the URI scheme. 28 | fn scheme(&self) -> Option<&Scheme>; 29 | 30 | /// Returns the URI authority. 31 | fn authority(&self) -> Option<&Authority>; 32 | 33 | /// Returns the URI path. 34 | fn path(&self) -> &str; 35 | 36 | /// Splits a protobuf RPC request path into routing prefix, service name, 37 | /// and method name. 38 | /// 39 | /// Returns `None` if the request path does not contain a `/`. 40 | fn protobuf_rpc_parts(&self) -> Option<(&str, &str, &str)> { 41 | let (prefix, method) = self.path().rsplit_once('/')?; 42 | let (routing_prefix, service) = prefix.rsplit_once('/')?; 43 | Some((routing_prefix, service, method)) 44 | } 45 | 46 | /// Returns the message codec. 47 | fn message_codec(&self) -> Result<&str, Error>; 48 | 49 | /// Returns the timeout. 50 | fn timeout(&self) -> Option; 51 | 52 | /// Returns the content encoding (e.g. compression). 53 | fn content_encoding(&self) -> Option<&str>; 54 | 55 | /// Returns the accept encoding(s). 56 | fn accept_encoding(&self) -> impl Iterator; 57 | 58 | /// Returns the metadata. 59 | fn metadata(&self) -> &impl Metadata; 60 | 61 | /// Validates the request. 62 | fn validate(&self) -> Result<(), Error>; 63 | } 64 | 65 | /// Connect request types. 66 | pub enum ConnectRequestType { 67 | Unary(UnaryRequest), 68 | Streaming(StreamingRequest), 69 | UnaryGet(UnaryGetRequest), 70 | } 71 | 72 | impl ConnectRequestType { 73 | pub fn from_http(req: http::Request) -> Self { 74 | if req.method() == Method::GET { 75 | Self::UnaryGet(req.map(|_| ()).into()) 76 | } else if req.headers().get(header::CONTENT_TYPE).is_some_and(|ct| { 77 | ct.to_str() 78 | .unwrap_or_default() 79 | .starts_with(STREAMING_CONTENT_TYPE_PREFIX) 80 | }) { 81 | Self::Streaming(req.into()) 82 | } else { 83 | Self::Unary(req.into()) 84 | } 85 | } 86 | } 87 | 88 | /// A [`ConnectRequest`] backed by an [`http::Request`] 89 | trait HttpConnectRequest { 90 | fn http_uri(&self) -> &Uri; 91 | 92 | fn http_headers(&self) -> &HeaderMap; 93 | 94 | fn http_message_codec(&self) -> Result<&str, Error>; 95 | 96 | fn http_connect_protocol_version(&self) -> Option<&str> { 97 | self.http_headers() 98 | .get(CONNECT_PROTOCOL_VERSION)? 99 | .to_str() 100 | .ok() 101 | } 102 | 103 | fn http_content_encoding(&self) -> Option<&str>; 104 | 105 | fn http_accept_encoding(&self) -> impl Iterator { 106 | self.http_headers() 107 | .get_all(header::ACCEPT_ENCODING) 108 | .into_iter() 109 | .filter_map(|val| val.to_str().ok()) 110 | } 111 | 112 | fn http_validate(&self) -> Result<(), Error> 113 | where 114 | Self: Sized, 115 | { 116 | validate_request(self) 117 | } 118 | } 119 | 120 | fn validate_request(req: &impl HttpConnectRequest) -> Result<(), Error> { 121 | match req.http_connect_protocol_version() { 122 | None => (), 123 | Some(ver) if ver == PROTOCOL_VERSION_1 => (), 124 | Some(ver) => { 125 | return Err(Error::InvalidRequest(format!( 126 | "unknown connect-protocol-version {ver:?}" 127 | ))); 128 | } 129 | } 130 | let _ = req.http_message_codec()?; 131 | Ok(()) 132 | } 133 | 134 | impl ConnectRequest for T { 135 | fn connect_protocol_version(&self) -> Option<&str> { 136 | HttpConnectRequest::http_connect_protocol_version(self) 137 | } 138 | 139 | fn scheme(&self) -> Option<&Scheme> { 140 | self.http_uri().scheme() 141 | } 142 | 143 | fn authority(&self) -> Option<&Authority> { 144 | self.http_uri().authority() 145 | } 146 | 147 | fn path(&self) -> &str { 148 | self.http_uri().path() 149 | } 150 | 151 | fn message_codec(&self) -> Result<&str, Error> { 152 | self.http_message_codec() 153 | } 154 | 155 | fn timeout(&self) -> Option { 156 | let timeout_ms: u64 = self 157 | .http_headers() 158 | .get(CONNECT_TIMEOUT_MS)? 159 | .to_str() 160 | .ok()? 161 | .parse() 162 | .ok()?; 163 | Some(Duration::from_millis(timeout_ms)) 164 | } 165 | 166 | fn content_encoding(&self) -> Option<&str> { 167 | self.http_content_encoding() 168 | } 169 | 170 | fn accept_encoding(&self) -> impl Iterator { 171 | self.http_accept_encoding() 172 | } 173 | 174 | fn metadata(&self) -> &impl Metadata { 175 | self.http_headers() 176 | } 177 | 178 | fn validate(&self) -> Result<(), Error> { 179 | self.http_validate() 180 | } 181 | } 182 | 183 | /// A Connect unary request. 184 | pub struct UnaryRequest(http::Request); 185 | 186 | impl HttpConnectRequest for UnaryRequest { 187 | fn http_uri(&self) -> &Uri { 188 | self.0.uri() 189 | } 190 | 191 | fn http_headers(&self) -> &HeaderMap { 192 | self.0.headers() 193 | } 194 | 195 | fn http_message_codec(&self) -> Result<&str, Error> { 196 | unary_message_codec(self.http_headers()) 197 | } 198 | 199 | fn http_content_encoding(&self) -> Option<&str> { 200 | self.http_headers() 201 | .get(header::CONTENT_ENCODING)? 202 | .to_str() 203 | .ok() 204 | } 205 | } 206 | 207 | impl From> for UnaryRequest { 208 | fn from(req: http::Request) -> Self { 209 | Self(req) 210 | } 211 | } 212 | 213 | impl From> for http::Request { 214 | fn from(req: UnaryRequest) -> Self { 215 | req.0 216 | } 217 | } 218 | 219 | /// A Connect streaming request. 220 | pub struct StreamingRequest(http::Request); 221 | 222 | impl HttpConnectRequest for StreamingRequest { 223 | fn http_uri(&self) -> &Uri { 224 | self.0.uri() 225 | } 226 | 227 | fn http_headers(&self) -> &HeaderMap { 228 | self.0.headers() 229 | } 230 | 231 | fn http_message_codec(&self) -> Result<&str, Error> { 232 | streaming_message_codec(self.http_headers()) 233 | } 234 | 235 | fn http_content_encoding(&self) -> Option<&str> { 236 | self.http_headers() 237 | .get(CONNECT_CONTENT_ENCODING)? 238 | .to_str() 239 | .ok() 240 | } 241 | 242 | fn http_accept_encoding(&self) -> impl Iterator { 243 | self.http_headers() 244 | .get_all(CONNECT_ACCEPT_ENCODING) 245 | .into_iter() 246 | .filter_map(|val| val.to_str().ok()) 247 | } 248 | } 249 | 250 | impl From> for StreamingRequest { 251 | fn from(req: http::Request) -> Self { 252 | Self(req) 253 | } 254 | } 255 | 256 | impl From> for http::Request { 257 | fn from(req: StreamingRequest) -> Self { 258 | req.0 259 | } 260 | } 261 | 262 | /// A Connect unary GET request. 263 | pub struct UnaryGetRequest { 264 | inner: http::Request<()>, 265 | query: HashMap, 266 | } 267 | 268 | impl UnaryGetRequest { 269 | pub fn message(&self) -> Result, Error> { 270 | let message = self 271 | .query 272 | .get("message") 273 | .ok_or(Error::invalid_request("missing message"))?; 274 | let is_b64 = self.query.get("base64").map(|s| s.as_str()) == Some("1"); 275 | if is_b64 { 276 | Ok(BASE64_URL_SAFE_NO_PAD.decode(message)?.into()) 277 | } else { 278 | Ok( 279 | match percent_encoding::percent_decode_str(message) 280 | .decode_utf8() 281 | .map_err(|_| Error::invalid_request("message not valid utf8"))? 282 | { 283 | Cow::Borrowed(s) => s.as_bytes().into(), 284 | Cow::Owned(s) => s.into_bytes().into(), 285 | }, 286 | ) 287 | } 288 | } 289 | } 290 | 291 | impl HttpConnectRequest for UnaryGetRequest { 292 | fn http_uri(&self) -> &Uri { 293 | self.inner.uri() 294 | } 295 | 296 | fn http_headers(&self) -> &HeaderMap { 297 | self.inner.headers() 298 | } 299 | 300 | fn http_message_codec(&self) -> Result<&str, Error> { 301 | self.query 302 | .get("encoding") 303 | .map(|s| s.as_str()) 304 | .ok_or(Error::invalid_request("missing 'encoding' param")) 305 | } 306 | 307 | fn http_connect_protocol_version(&self) -> Option<&str> { 308 | self.query.get("connect")?.strip_prefix("v") 309 | } 310 | 311 | fn http_content_encoding(&self) -> Option<&str> { 312 | self.query.get("encoding").map(|s| s.as_str()) 313 | } 314 | 315 | fn http_validate(&self) -> Result<(), Error> 316 | where 317 | Self: Sized, 318 | { 319 | validate_request(self)?; 320 | if !self.query.contains_key("message") { 321 | return Err(Error::invalid_request("missing 'message' param")); 322 | } 323 | Ok(()) 324 | } 325 | } 326 | 327 | impl From> for UnaryGetRequest { 328 | fn from(req: http::Request<()>) -> Self { 329 | let query: HashMap<_, _> = 330 | form_urlencoded::parse(req.uri().query().unwrap_or_default().as_bytes()) 331 | .map(|(k, v)| (k.to_string(), v.to_string())) 332 | .collect(); 333 | Self { inner: req, query } 334 | } 335 | } 336 | 337 | impl From for http::Request<()> { 338 | fn from(req: UnaryGetRequest) -> Self { 339 | req.inner 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/request/builder.rs: -------------------------------------------------------------------------------- 1 | use http::{ 2 | header, 3 | uri::{Authority, Parts, PathAndQuery, Scheme}, 4 | HeaderMap, HeaderName, HeaderValue, Method, Request, Uri, 5 | }; 6 | 7 | use base64::{engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_SAFE, Engine}; 8 | 9 | use crate::{ 10 | common::{ 11 | is_valid_http_token, CONNECT_ACCEPT_ENCODING, CONNECT_CONTENT_ENCODING, 12 | CONNECT_PROTOCOL_VERSION, CONNECT_TIMEOUT_MS, CONTENT_TYPE_PREFIX, PROTOCOL_VERSION_1, 13 | }, 14 | metadata::Metadata, 15 | Error, 16 | }; 17 | 18 | use super::{StreamingRequest, UnaryGetRequest, UnaryRequest}; 19 | 20 | #[derive(Debug, Default)] 21 | pub struct RequestBuilder { 22 | scheme: Option, 23 | authority: Option, 24 | path: Option, 25 | metadata: HeaderMap, 26 | message_codec: Option, 27 | timeout_ms: Option, 28 | content_encoding: Option, 29 | accept_encoding: Vec, 30 | } 31 | 32 | impl RequestBuilder { 33 | /// Sets the URI scheme for this request. 34 | /// 35 | /// Defaults to [`Scheme::HTTPS`]. 36 | pub fn scheme( 37 | mut self, 38 | scheme: impl TryInto>, 39 | ) -> Result { 40 | self.scheme = Some(scheme.try_into().map_err(Into::into)?); 41 | Ok(self) 42 | } 43 | 44 | /// Sets the authority (e.g. hostname) for this request. 45 | pub fn authority( 46 | mut self, 47 | authority: impl TryInto>, 48 | ) -> Result { 49 | self.authority = Some(authority.try_into().map_err(Into::into)?); 50 | Ok(self) 51 | } 52 | 53 | /// Sets the path for this request. 54 | /// 55 | /// May not contain query params (i.e. the character '?'). 56 | /// 57 | /// See also [`Self::protobuf_rpc`]. 58 | pub fn path(mut self, path: impl Into) -> Result { 59 | let mut path = path.into(); 60 | if path.contains('?') { 61 | return Err(Error::invalid_request( 62 | "path may not contain query params ('?')", 63 | )); 64 | } 65 | if !path.starts_with('/') { 66 | path = format!("/{path}"); 67 | } 68 | self.path = Some(path); 69 | Ok(self) 70 | } 71 | 72 | /// Sets the path for this request from a protobuf RPC service/method. 73 | /// 74 | /// See also [`Self::protobuf_rpc_with_routing_prefix`]. 75 | pub fn protobuf_rpc( 76 | self, 77 | full_service_name: impl AsRef, 78 | method_name: impl AsRef, 79 | ) -> Result { 80 | self.path(format!( 81 | "/{}/{}", 82 | full_service_name.as_ref(), 83 | method_name.as_ref() 84 | )) 85 | } 86 | 87 | /// Sets the path for this request from a routing prefix and protobuf RPC 88 | /// service/method. 89 | pub fn protobuf_rpc_with_routing_prefix( 90 | self, 91 | routing_prefix: impl Into, 92 | full_service_name: impl AsRef, 93 | method_name: impl AsRef, 94 | ) -> Result { 95 | let mut routing_prefix = routing_prefix.into(); 96 | if !routing_prefix.ends_with('/') { 97 | routing_prefix = format!("{routing_prefix}/"); 98 | } 99 | self.path(format!( 100 | "{routing_prefix}{}/{}", 101 | full_service_name.as_ref(), 102 | method_name.as_ref() 103 | )) 104 | } 105 | 106 | /// Sets the scheme, authority, and path for this request from a URI. 107 | /// 108 | /// Any query part of the URI is discarded. 109 | pub fn uri(mut self, uri: impl TryInto>) -> Result { 110 | let uri: Uri = uri.try_into().map_err(Into::into)?; 111 | let Parts { 112 | scheme, 113 | authority, 114 | path_and_query, 115 | .. 116 | } = uri.into_parts(); 117 | self.scheme = scheme; 118 | self.authority = authority; 119 | self.path = path_and_query.map(|paq| paq.path().to_string()); 120 | Ok(self) 121 | } 122 | 123 | /// Appends ASCII metadata to the request. 124 | pub fn ascii_metadata( 125 | mut self, 126 | key: impl TryInto>, 127 | val: impl Into, 128 | ) -> Result { 129 | self.metadata.append_ascii(key, val)?; 130 | Ok(self) 131 | } 132 | 133 | /// Appends binary metadata to the request. 134 | pub fn binary_metadata( 135 | mut self, 136 | key: impl TryInto>, 137 | val: impl AsRef<[u8]>, 138 | ) -> Result { 139 | self.metadata.append_binary(key, val)?; 140 | Ok(self) 141 | } 142 | 143 | /// Sets the message codec for this request. 144 | /// 145 | /// Typical codecs are 'json' and 'proto', corresponding to the 146 | /// `content-type`s `application/json` and `application/proto`. 147 | /// 148 | /// The caller is responsible for making sure the request payload matches 149 | /// this message codec. 150 | pub fn message_codec(mut self, message_codec: impl Into) -> Result { 151 | let mut message_codec: String = message_codec.into(); 152 | message_codec.make_ascii_lowercase(); 153 | if !is_valid_http_token(&message_codec) { 154 | return Err(Error::invalid_request("invalid message codec")); 155 | } 156 | self.message_codec = Some(message_codec); 157 | Ok(self) 158 | } 159 | 160 | /// Sets the request timeout in milliseconds. 161 | pub fn timeout_ms(mut self, timeout_ms: u64) -> Result { 162 | // Timeout-Milliseconds → {positive integer as ASCII string of at most 10 digits} 163 | let timeout = timeout_ms.to_string(); 164 | if timeout.len() > 10 { 165 | return Err(Error::invalid_request("timeout too large")); 166 | } 167 | self.timeout_ms = Some(timeout.try_into().unwrap()); 168 | Ok(self) 169 | } 170 | 171 | /// Clears the request timeout. 172 | pub fn clear_timeout(mut self) -> Self { 173 | self.timeout_ms = None; 174 | self 175 | } 176 | 177 | /// Sets the request content encoding (e.g. compression). 178 | pub fn content_encoding(mut self, content_encoding: impl Into) -> Result { 179 | let content_encoding = content_encoding.into(); 180 | if !is_valid_http_token(&content_encoding) { 181 | return Err(Error::invalid_request("invalid content encoding")); 182 | } 183 | self.content_encoding = Some(content_encoding); 184 | Ok(self) 185 | } 186 | 187 | /// Sets the request accept encoding(s). 188 | pub fn accept_encoding>>( 189 | mut self, 190 | accept_encodings: impl IntoIterator, 191 | ) -> Result { 192 | self.accept_encoding = accept_encodings 193 | .into_iter() 194 | .map(|v| v.try_into().map_err(Into::into)) 195 | .collect::>()?; 196 | Ok(self) 197 | } 198 | 199 | /// Build logic common to all requests. 200 | fn common_request(&mut self, method: Method, body: T) -> Result, Error> { 201 | let mut req = Request::new(body); 202 | *req.method_mut() = method; 203 | let mut headers: HeaderMap = std::mem::take(&mut self.metadata); 204 | // Connect-Protocol-Version → "connect-protocol-version" "1" 205 | headers.insert(CONNECT_PROTOCOL_VERSION, PROTOCOL_VERSION_1); 206 | // Timeout → "connect-timeout-ms" Timeout-Milliseconds 207 | if let Some(timeout) = self.timeout_ms.take() { 208 | headers.insert(CONNECT_TIMEOUT_MS, timeout); 209 | } 210 | *req.headers_mut() = headers; 211 | Ok(req) 212 | } 213 | 214 | /// Builds a [`UnaryRequest`]. 215 | /// 216 | /// See: https://connectrpc.com/docs/protocol/#unary-request 217 | pub fn unary(mut self, body: T) -> Result, Error> { 218 | let mut req = self.common_request(Method::POST, body)?; 219 | *req.uri_mut() = build_uri(self.scheme, self.authority, self.path)?; 220 | 221 | // Unary-Content-Type → "content-type" "application/" Message-Codec 222 | if let Some(message_codec) = &self.message_codec { 223 | req.headers_mut().insert( 224 | header::CONTENT_TYPE, 225 | (format!("{CONTENT_TYPE_PREFIX}{message_codec}")).try_into()?, 226 | ); 227 | } 228 | // Content-Encoding → "content-encoding" Content-Coding 229 | if let Some(content_encoding) = self.content_encoding.take() { 230 | req.headers_mut() 231 | .insert(header::CONTENT_ENCODING, content_encoding.try_into()?); 232 | } 233 | // Accept-Encoding → "accept-encoding" Content-Coding [...] 234 | for value in std::mem::take(&mut self.accept_encoding) { 235 | req.headers_mut().append(header::ACCEPT_ENCODING, value); 236 | } 237 | Ok(req.into()) 238 | } 239 | 240 | /// Builds a [`StreamingRequest`]. 241 | /// 242 | /// https://connectrpc.com/docs/protocol/#streaming-request 243 | pub fn streaming(mut self, body: T) -> Result, Error> { 244 | let mut req = self.common_request(Method::POST, body)?; 245 | *req.uri_mut() = build_uri(self.scheme, self.authority, self.path)?; 246 | 247 | // Streaming-Content-Type → "content-type" "application/connect+" [...] 248 | if let Some(message_codec) = &self.message_codec { 249 | req.headers_mut().insert( 250 | header::CONTENT_TYPE, 251 | (format!("{CONTENT_TYPE_PREFIX}{message_codec}")).try_into()?, 252 | ); 253 | } 254 | // Streaming-Content-Encoding → "connect-content-encoding" Content-Coding 255 | if let Some(content_encoding) = self.content_encoding.take() { 256 | req.headers_mut() 257 | .insert(CONNECT_CONTENT_ENCODING, content_encoding.try_into()?); 258 | } 259 | // Streaming-Accept-Encoding → "connect-accept-encoding" Content-Coding [...] 260 | for value in std::mem::take(&mut self.accept_encoding) { 261 | req.headers_mut().append(CONNECT_ACCEPT_ENCODING, value); 262 | } 263 | Ok(req.into()) 264 | } 265 | 266 | /// Builds a [`UnaryGetRequest`]. 267 | /// 268 | // https://connectrpc.com/docs/protocol/#unary-get-request 269 | pub fn unary_get(mut self, message: impl AsRef<[u8]>) -> Result { 270 | let mut req = self.common_request(Method::GET, ())?; 271 | *req.method_mut() = Method::GET; 272 | 273 | let path_and_query = { 274 | let path = self.path.ok_or(Error::invalid_request("path required"))?; 275 | let query = { 276 | let mut query = form_urlencoded::Serializer::new("?".to_string()); 277 | query 278 | // Message-Query → "message=" (*{percent-encoded octet}) 279 | .append_pair("message", &BASE64_URL_SAFE.encode(message)) 280 | // Base64-Query → "&base64=1" 281 | .append_pair("base64", "1") 282 | // Connect-Version-Query → "&connect=v1" 283 | .append_pair("connect", "v1"); 284 | if let Some(message_codec) = &self.message_codec { 285 | // Encoding-Query → "&encoding=" Message-Codec 286 | query.append_pair("encoding", message_codec); 287 | } else { 288 | return Err(Error::invalid_request("message codec required")); 289 | } 290 | if let Some(content_encoding) = &self.content_encoding { 291 | // Compression-Query → "&compression=" Content-Coding 292 | query.append_pair("compression", content_encoding); 293 | } 294 | query.finish() 295 | }; 296 | Some(format!("{path}?{query}")) 297 | }; 298 | *req.uri_mut() = build_uri(self.scheme, self.authority, path_and_query)?; 299 | 300 | // Accept-Encoding (same as unary) 301 | for value in std::mem::take(&mut self.accept_encoding) { 302 | req.headers_mut().append(header::ACCEPT_ENCODING, value); 303 | } 304 | Ok(req.into()) 305 | } 306 | } 307 | 308 | fn build_uri( 309 | scheme: Option, 310 | authority: Option, 311 | path_and_query: Option>>, 312 | ) -> Result { 313 | Ok(Uri::from_parts({ 314 | let mut parts = Parts::default(); 315 | parts.scheme = scheme; 316 | parts.authority = authority; 317 | parts.path_and_query = path_and_query 318 | .map(TryInto::try_into) 319 | .transpose() 320 | .map_err(Into::into)?; 321 | parts 322 | })?) 323 | } 324 | -------------------------------------------------------------------------------- /src/reqwest.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::{ 6 | request::{ConnectRequest, UnaryGetRequest, UnaryRequest}, 7 | response::{ 8 | error::{ConnectCode, ConnectError}, 9 | UnaryResponse, ValidateOpts, 10 | }, 11 | Error, 12 | }; 13 | 14 | pub trait ReqwestClientExt { 15 | /// Executes a Connect RPC [`UnaryRequest`]. 16 | fn execute_unary( 17 | &self, 18 | req: UnaryRequest>, 19 | ) -> impl Future, Error>>; 20 | 21 | /// Executes a Connect RPC [`UnaryGetRequest`]. 22 | fn execute_unary_get( 23 | &self, 24 | req: UnaryGetRequest, 25 | ) -> impl Future, Error>>; 26 | } 27 | 28 | impl ReqwestClientExt for reqwest::Client { 29 | async fn execute_unary( 30 | &self, 31 | req: UnaryRequest>, 32 | ) -> Result, Error> { 33 | let validate_opts = ValidateOpts::from_request(&req); 34 | let resp = self.execute(req.try_into()?).await?; 35 | let connect_resp: UnaryResponse<_> = response_to_http_bytes(resp).await?.into(); 36 | connect_resp.result(&validate_opts) 37 | } 38 | 39 | async fn execute_unary_get(&self, req: UnaryGetRequest) -> Result, Error> { 40 | let validate_opts = ValidateOpts::from_request(&req); 41 | let resp = self.execute(req.try_into()?).await?; 42 | let connect_resp: UnaryResponse<_> = response_to_http_bytes(resp).await?.into(); 43 | connect_resp.result(&validate_opts) 44 | } 45 | } 46 | 47 | async fn response_to_http_bytes( 48 | mut resp: reqwest::Response, 49 | ) -> Result, Error> { 50 | let status = resp.status(); 51 | let headers = std::mem::take(resp.headers_mut()); 52 | let body = resp.bytes().await?; 53 | let mut http_resp = http::Response::new(body); 54 | *http_resp.status_mut() = status; 55 | *http_resp.headers_mut() = headers; 56 | Ok(http_resp) 57 | } 58 | 59 | impl> TryFrom> for reqwest::Request { 60 | type Error = Error; 61 | 62 | fn try_from(req: UnaryRequest) -> Result { 63 | let timeout = req.timeout(); 64 | let mut req = reqwest::Request::try_from(http::Request::from(req))?; 65 | *req.timeout_mut() = timeout; 66 | Ok(req) 67 | } 68 | } 69 | 70 | impl TryFrom for reqwest::Request { 71 | type Error = Error; 72 | 73 | fn try_from(req: UnaryGetRequest) -> Result { 74 | let timeout = req.timeout(); 75 | let http_req = http::Request::from(req).map(|()| reqwest::Body::default()); 76 | let mut req = reqwest::Request::try_from(http_req)?; 77 | *req.timeout_mut() = timeout; 78 | Ok(req) 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(err: reqwest::Error) -> Self { 84 | if err.is_timeout() { 85 | Self::ConnectError(ConnectError::new( 86 | ConnectCode::DeadlineExceeded, 87 | "request timed out", 88 | )) 89 | } else { 90 | Self::ReqwestError(err) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | pub mod builder; 2 | pub mod error; 3 | 4 | use http::{header, HeaderMap, StatusCode}; 5 | 6 | use crate::{ 7 | common::{ 8 | streaming_message_codec, unary_message_codec, CONNECT_CONTENT_ENCODING, 9 | CONTENT_ENCODING_IDENTITY, 10 | }, 11 | metadata::Metadata, 12 | request::ConnectRequest, 13 | Error, 14 | }; 15 | 16 | /// A Connect response. 17 | pub trait ConnectResponse { 18 | /// Returns the status code. 19 | fn status(&self) -> StatusCode; 20 | 21 | /// Returns the message codec. 22 | fn message_codec(&self) -> Result<&str, Error>; 23 | 24 | /// Returns the content encoding. 25 | fn content_encoding(&self) -> Option<&str>; 26 | 27 | /// Returns a reference to the metadata. 28 | fn metadata(&self) -> &impl Metadata; 29 | 30 | /// Validates the response. 31 | fn validate(&self, opts: &ValidateOpts) -> Result<(), Error>; 32 | } 33 | 34 | /// Options for [`ConnectResponse::validate`]. 35 | #[derive(Clone, Debug, Default)] 36 | pub struct ValidateOpts { 37 | /// If given, the response message codec must match. 38 | pub message_codec: Option, 39 | /// If given, the response content encoding must match (or be 'identity'). 40 | pub accept_encoding: Option>, 41 | } 42 | 43 | impl ValidateOpts { 44 | pub fn from_request(req: &impl ConnectRequest) -> Self { 45 | let message_codec = req.message_codec().map(ToString::to_string).ok(); 46 | let accept_encoding = Some(req.accept_encoding().map(ToString::to_string).collect()); 47 | Self { 48 | message_codec, 49 | accept_encoding, 50 | } 51 | } 52 | } 53 | 54 | trait HttpConnectResponse { 55 | fn http_status(&self) -> StatusCode; 56 | 57 | fn http_headers(&self) -> &HeaderMap; 58 | 59 | fn http_message_codec(&self) -> Result<&str, Error>; 60 | 61 | fn http_content_encoding(&self) -> Option<&str>; 62 | } 63 | 64 | impl ConnectResponse for T { 65 | fn status(&self) -> StatusCode { 66 | self.http_status() 67 | } 68 | 69 | fn message_codec(&self) -> Result<&str, Error> { 70 | self.http_message_codec() 71 | } 72 | 73 | fn content_encoding(&self) -> Option<&str> { 74 | self.http_content_encoding() 75 | } 76 | 77 | fn metadata(&self) -> &impl Metadata { 78 | self.http_headers() 79 | } 80 | 81 | fn validate(&self, opts: &ValidateOpts) -> Result<(), Error> { 82 | let codec = self.message_codec()?; 83 | if let Some(validate_codec) = &opts.message_codec { 84 | if codec != validate_codec { 85 | return Err(Error::UnexpectedMessageCodec(codec.into())); 86 | } 87 | } 88 | if let Some(encoding) = self.content_encoding() { 89 | if encoding != CONTENT_ENCODING_IDENTITY { 90 | if let Some(accept_encoding) = &opts.accept_encoding { 91 | if !accept_encoding.iter().any(|accept| accept == encoding) { 92 | return Err(Error::UnacceptableEncoding(encoding.into())); 93 | } 94 | } 95 | } 96 | } 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[derive(Clone, Debug)] 102 | pub struct UnaryResponse(http::Response); 103 | 104 | impl UnaryResponse { 105 | pub fn body(&self) -> &T { 106 | self.0.body() 107 | } 108 | } 109 | 110 | impl> UnaryResponse { 111 | pub fn result(self, validate_opts: &ValidateOpts) -> Result { 112 | if !self.0.status().is_success() { 113 | return Err(Error::ConnectError(http::Response::from(self).into())); 114 | } 115 | self.validate(validate_opts)?; 116 | Ok(self) 117 | } 118 | } 119 | 120 | impl HttpConnectResponse for UnaryResponse { 121 | fn http_status(&self) -> StatusCode { 122 | self.0.status() 123 | } 124 | 125 | fn http_headers(&self) -> &HeaderMap { 126 | self.0.headers() 127 | } 128 | 129 | fn http_message_codec(&self) -> Result<&str, Error> { 130 | unary_message_codec(self.http_headers()) 131 | } 132 | 133 | fn http_content_encoding(&self) -> Option<&str> { 134 | self.http_headers() 135 | .get(header::CONTENT_ENCODING)? 136 | .to_str() 137 | .ok() 138 | } 139 | } 140 | 141 | impl From> for UnaryResponse { 142 | fn from(resp: http::Response) -> Self { 143 | Self(resp) 144 | } 145 | } 146 | 147 | impl From> for http::Response { 148 | fn from(resp: UnaryResponse) -> Self { 149 | resp.0 150 | } 151 | } 152 | 153 | #[derive(Clone, Debug)] 154 | pub struct StreamingResponse(http::Response); 155 | 156 | impl HttpConnectResponse for StreamingResponse { 157 | fn http_status(&self) -> StatusCode { 158 | self.0.status() 159 | } 160 | 161 | fn http_headers(&self) -> &HeaderMap { 162 | self.0.headers() 163 | } 164 | 165 | fn http_message_codec(&self) -> Result<&str, Error> { 166 | streaming_message_codec(self.http_headers()) 167 | } 168 | 169 | fn http_content_encoding(&self) -> Option<&str> { 170 | self.http_headers() 171 | .get(CONNECT_CONTENT_ENCODING)? 172 | .to_str() 173 | .ok() 174 | } 175 | } 176 | 177 | impl From> for StreamingResponse { 178 | fn from(resp: http::Response) -> Self { 179 | Self(resp) 180 | } 181 | } 182 | 183 | impl From> for http::Response { 184 | fn from(resp: StreamingResponse) -> Self { 185 | resp.0 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/response/builder.rs: -------------------------------------------------------------------------------- 1 | use http::{header, HeaderMap, HeaderName, StatusCode}; 2 | 3 | use crate::{ 4 | common::{is_valid_http_token, CONNECT_CONTENT_ENCODING, CONTENT_TYPE_PREFIX}, 5 | metadata::Metadata, 6 | Error, 7 | }; 8 | 9 | use super::{StreamingResponse, UnaryResponse}; 10 | 11 | #[derive(Debug, Default)] 12 | pub struct ResponseBuilder { 13 | status: StatusCode, 14 | metadata: HeaderMap, 15 | message_codec: Option, 16 | content_encoding: Option, 17 | } 18 | 19 | impl ResponseBuilder { 20 | /// Sets the response status code. 21 | pub fn status(mut self, status: StatusCode) -> Self { 22 | self.status = status; 23 | self 24 | } 25 | 26 | /// Appends ASCII metadata to the response. 27 | pub fn ascii_metadata( 28 | mut self, 29 | key: impl TryInto>, 30 | val: impl Into, 31 | ) -> Result { 32 | self.metadata.append_ascii(key, val)?; 33 | Ok(self) 34 | } 35 | 36 | /// Appends binary metadata to the response. 37 | pub fn binary_metadata( 38 | mut self, 39 | key: impl TryInto>, 40 | val: impl AsRef<[u8]>, 41 | ) -> Result { 42 | self.metadata.append_binary(key, val)?; 43 | Ok(self) 44 | } 45 | 46 | /// Sets the message codec for this response. 47 | /// 48 | /// Typical codecs are 'json' and 'proto', corresponding to the 49 | /// `content-type`s `application/json` and `application/proto`. 50 | /// 51 | /// The caller is responsible for making sure the response payload matches 52 | /// this message codec. 53 | pub fn message_codec(mut self, message_codec: impl Into) -> Result { 54 | let mut message_codec: String = message_codec.into(); 55 | message_codec.make_ascii_lowercase(); 56 | if !is_valid_http_token(&message_codec) { 57 | return Err(Error::invalid_request("invalid message codec")); 58 | } 59 | self.message_codec = Some(message_codec); 60 | Ok(self) 61 | } 62 | 63 | /// Sets the response content encoding (e.g. compression). 64 | pub fn content_encoding(mut self, content_encoding: impl Into) -> Result { 65 | let content_encoding = content_encoding.into(); 66 | if !is_valid_http_token(&content_encoding) { 67 | return Err(Error::invalid_request("invalid content encoding")); 68 | } 69 | self.content_encoding = Some(content_encoding); 70 | Ok(self) 71 | } 72 | 73 | /// Build logic common to all responses. 74 | fn common_response(&mut self, body: T) -> http::Response { 75 | let mut resp = http::Response::new(body); 76 | *resp.status_mut() = self.status; 77 | *resp.headers_mut() = std::mem::take(&mut self.metadata); 78 | resp 79 | } 80 | 81 | /// Builds a [`UnaryResponse`]. 82 | pub fn unary(mut self, body: T) -> Result, Error> { 83 | let mut resp = self.common_response(body); 84 | // Unary-Content-Type → "content-type" "application/" Message-Codec 85 | if let Some(message_codec) = &self.message_codec { 86 | resp.headers_mut().insert( 87 | header::CONTENT_TYPE, 88 | (format!("{CONTENT_TYPE_PREFIX}{message_codec}")).try_into()?, 89 | ); 90 | } 91 | // Content-Encoding → "content-encoding" Content-Coding 92 | if let Some(content_encoding) = self.content_encoding.take() { 93 | resp.headers_mut() 94 | .insert(header::CONTENT_ENCODING, content_encoding.try_into()?); 95 | } 96 | Ok(resp.into()) 97 | } 98 | 99 | /// Builds a [`StreamingResponse`]. 100 | pub fn streaming(mut self, body: T) -> Result, Error> { 101 | let mut resp = self.common_response(body); 102 | // Streaming-Content-Type → "content-type" "application/connect+" [...] 103 | if let Some(message_codec) = &self.message_codec { 104 | resp.headers_mut().insert( 105 | header::CONTENT_TYPE, 106 | (format!("{CONTENT_TYPE_PREFIX}{message_codec}")).try_into()?, 107 | ); 108 | } 109 | // Streaming-Content-Encoding → "connect-content-encoding" Content-Coding 110 | if let Some(content_encoding) = self.content_encoding.take() { 111 | resp.headers_mut() 112 | .insert(CONNECT_CONTENT_ENCODING, content_encoding.try_into()?); 113 | } 114 | Ok(resp.into()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/response/error.rs: -------------------------------------------------------------------------------- 1 | use http::{header, HeaderMap, HeaderValue}; 2 | 3 | use crate::{common::base64_decode, metadata::Metadata, Error}; 4 | 5 | const ERROR_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/json"); 6 | 7 | /// A Connect error. 8 | #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] 9 | pub struct ConnectError { 10 | #[serde(default, deserialize_with = "deserialize_error_code")] 11 | code: Option, 12 | #[serde(default, skip_serializing_if = "String::is_empty")] 13 | pub message: String, 14 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 15 | pub details: Vec, 16 | #[serde(skip)] 17 | headers: HeaderMap, 18 | } 19 | 20 | impl ConnectError { 21 | pub fn new(code: ConnectCode, message: impl std::fmt::Display) -> Self { 22 | Self { 23 | code: Some(code), 24 | message: message.to_string(), 25 | details: Default::default(), 26 | headers: Default::default(), 27 | } 28 | } 29 | 30 | pub fn code(&self) -> ConnectCode { 31 | self.code.unwrap_or(ConnectCode::Unknown) 32 | } 33 | 34 | pub fn metadata(&self) -> &impl Metadata { 35 | &self.headers 36 | } 37 | } 38 | 39 | impl std::fmt::Display for ConnectError { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | f.write_str(serde_json::to_value(self.code()).unwrap().as_str().unwrap())?; 42 | if !self.message.is_empty() { 43 | write!(f, ": {}", self.message)?; 44 | } 45 | Ok(()) 46 | } 47 | } 48 | 49 | impl> From> for ConnectError { 50 | fn from(resp: http::Response) -> Self { 51 | let (parts, body) = resp.into_parts(); 52 | let error = if parts.headers.get(header::CONTENT_TYPE) == Some(&ERROR_CONTENT_TYPE) { 53 | match serde_json::from_slice::(body.as_ref()) { 54 | Ok(mut error) => { 55 | error.code.get_or_insert_with(|| parts.status.into()); 56 | Some(error) 57 | } 58 | Err(err) => { 59 | tracing::debug!(?err, "Failed to decode error JSON"); 60 | None 61 | } 62 | } 63 | } else { 64 | None 65 | }; 66 | let mut error = error.unwrap_or_else(|| Self::new(parts.status.into(), "request invalid")); 67 | error.headers = parts.headers; 68 | error 69 | } 70 | } 71 | 72 | impl From for ConnectError { 73 | fn from(err: Error) -> Self { 74 | let code = match err { 75 | Error::ConnectError(connect_error) => return connect_error, 76 | Error::InvalidResponse(_) 77 | | Error::UnacceptableEncoding(_) 78 | | Error::UnexpectedMessageCodec(_) => ConnectCode::Internal, 79 | _ => ConnectCode::Unknown, 80 | }; 81 | let message = match &err { 82 | Error::UnacceptableEncoding(_) | Error::UnexpectedMessageCodec(_) => err.to_string(), 83 | _ => "".into(), 84 | }; 85 | Self::new(code, message) 86 | } 87 | } 88 | 89 | fn deserialize_error_code<'de, D: serde::Deserializer<'de>>( 90 | deserializer: D, 91 | ) -> Result, D::Error> { 92 | use serde::Deserialize; 93 | Option::::deserialize(deserializer).or(Ok(None)) 94 | } 95 | 96 | /// ConnectCode represents categories of errors as codes. 97 | #[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)] 98 | #[serde(rename_all = "snake_case")] 99 | pub enum ConnectCode { 100 | /// The operation completed successfully. 101 | Ok, 102 | /// The operation was cancelled. 103 | Canceled, 104 | /// Unknown error. 105 | Unknown, 106 | /// Client specified an invalid argument. 107 | InvalidArgument, 108 | /// Deadline expired before operation could complete. 109 | DeadlineExceeded, 110 | /// Some requested entity was not found. 111 | NotFound, 112 | /// Some entity that we attempted to create already exists. 113 | AlreadyExists, 114 | /// The caller does not have permission to execute the specified operation. 115 | PermissionDenied, 116 | /// Some resource has been exhausted. 117 | ResourceExhausted, 118 | /// The system is not in a state required for the operation's execution. 119 | FailedPrecondition, 120 | /// The operation was aborted. 121 | Aborted, 122 | /// Operation was attempted past the valid range. 123 | OutOfRange, 124 | /// Operation is not implemented or not supported. 125 | Unimplemented, 126 | /// Internal error. 127 | Internal, 128 | /// The service is currently unavailable. 129 | Unavailable, 130 | /// Unrecoverable data loss or corruption. 131 | DataLoss, 132 | /// The request does not have valid authentication credentials 133 | Unauthenticated, 134 | } 135 | 136 | // https://connectrpc.com/docs/protocol/#http-to-error-code 137 | impl From for ConnectCode { 138 | fn from(code: http::StatusCode) -> Self { 139 | use http::StatusCode; 140 | match code { 141 | StatusCode::BAD_REQUEST => Self::Internal, 142 | StatusCode::UNAUTHORIZED => Self::Unauthenticated, 143 | StatusCode::FORBIDDEN => Self::PermissionDenied, 144 | StatusCode::NOT_FOUND => Self::Unimplemented, 145 | StatusCode::NOT_IMPLEMENTED => Self::Unimplemented, 146 | StatusCode::TOO_MANY_REQUESTS 147 | | StatusCode::BAD_GATEWAY 148 | | StatusCode::SERVICE_UNAVAILABLE 149 | | StatusCode::GATEWAY_TIMEOUT => Self::Unavailable, 150 | _ => Self::Unknown, 151 | } 152 | } 153 | } 154 | 155 | /// Connect error detail. 156 | #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] 157 | pub struct ConnectErrorDetail { 158 | #[serde(rename = "type")] 159 | pub proto_type: String, 160 | #[serde(rename = "value")] 161 | pub value_base64: String, 162 | } 163 | 164 | impl ConnectErrorDetail { 165 | pub fn type_url(&self) -> String { 166 | format!("type.googleapis.com/{}", self.proto_type) 167 | } 168 | 169 | pub fn value(&self) -> Result, Error> { 170 | base64_decode(&self.value_base64) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 | use futures_util::{stream, Stream, StreamExt, TryStream, TryStreamExt}; 3 | use http_body::Body; 4 | use http_body_util::BodyExt; 5 | 6 | use crate::{BoxError, Error}; 7 | 8 | pub struct ConnectFrame { 9 | pub compressed: bool, 10 | pub end: bool, 11 | pub data: Bytes, 12 | } 13 | 14 | const FLAGS_COMPRESSED: u8 = 0b1; 15 | const FLAGS_END: u8 = 0b01; 16 | 17 | impl ConnectFrame { 18 | pub fn body_stream(body: B) -> impl Stream> 19 | where 20 | B: Body>, 21 | { 22 | Self::bytes_stream(body.into_data_stream()) 23 | } 24 | 25 | pub fn bytes_stream(s: S) -> impl Stream> 26 | where 27 | S: TryStream>, 28 | { 29 | let mut parse_state = FrameParseState::default(); 30 | s.map_err(Error::body) 31 | .map(Some) 32 | .chain(stream::iter([None])) 33 | .flat_map(move |item| stream::iter(parse_state.feed(item))) 34 | } 35 | } 36 | 37 | #[derive(Default)] 38 | struct FrameParseState { 39 | buf: BytesMut, 40 | failed: bool, 41 | } 42 | 43 | impl FrameParseState { 44 | fn feed(&mut self, item: Option>) -> Vec> { 45 | if self.failed { 46 | return vec![]; 47 | } 48 | let data = match item { 49 | Some(Ok(data)) => data, 50 | Some(Err(err)) => { 51 | self.failed = true; 52 | return vec![Err(Error::body(err))]; 53 | } 54 | None => { 55 | if !self.buf.is_empty() { 56 | return vec![Err(Error::body("partial frame at end of stream"))]; 57 | } 58 | return vec![]; 59 | } 60 | }; 61 | 62 | self.buf.put(data); 63 | 64 | let mut frames = vec![]; 65 | loop { 66 | match self.parse_frame() { 67 | Ok(Some(frame)) => frames.push(Ok(frame)), 68 | Ok(None) => return frames, 69 | Err(err) => { 70 | self.failed = true; 71 | frames.push(Err(err)); 72 | } 73 | } 74 | } 75 | } 76 | 77 | fn parse_frame(&mut self) -> Result, Error> { 78 | if self.buf.len() < 5 { 79 | return Ok(None); 80 | } 81 | let data_len = (&self.buf[1..]).get_u32(); 82 | let Ok(frame_len) = ((data_len as u64) + 5).try_into() else { 83 | return Err(Error::body("frame too large")); 84 | }; 85 | if self.buf.len() < frame_len { 86 | return Ok(None); 87 | } 88 | let mut frame = self.buf.split_to(frame_len); 89 | let data = frame.split_off(5).freeze(); 90 | let flags = frame[0]; 91 | Ok(Some(ConnectFrame { 92 | compressed: flags & FLAGS_COMPRESSED != 0, 93 | end: flags & FLAGS_END != 0, 94 | data, 95 | })) 96 | } 97 | } 98 | --------------------------------------------------------------------------------