don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

mvp

Signed-off-by: tjh <did:plc:65gha4t3avpfpzmvpbwovss7>

tjh.dev c1e1b9c1

+7472
+2
.gitignore
··· 1 + /target 2 + did*/
+4198
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 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.1" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 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 = "allocator-api2" 31 + version = "0.2.21" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 + 35 + [[package]] 36 + name = "anstream" 37 + version = "0.6.20" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 40 + dependencies = [ 41 + "anstyle", 42 + "anstyle-parse", 43 + "anstyle-query", 44 + "anstyle-wincon", 45 + "colorchoice", 46 + "is_terminal_polyfill", 47 + "utf8parse", 48 + ] 49 + 50 + [[package]] 51 + name = "anstyle" 52 + version = "1.0.11" 53 + source = "registry+https://github.com/rust-lang/crates.io-index" 54 + checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 55 + 56 + [[package]] 57 + name = "anstyle-parse" 58 + version = "0.2.7" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 61 + dependencies = [ 62 + "utf8parse", 63 + ] 64 + 65 + [[package]] 66 + name = "anstyle-query" 67 + version = "1.1.4" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 70 + dependencies = [ 71 + "windows-sys 0.60.2", 72 + ] 73 + 74 + [[package]] 75 + name = "anstyle-wincon" 76 + version = "3.0.10" 77 + source = "registry+https://github.com/rust-lang/crates.io-index" 78 + checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 79 + dependencies = [ 80 + "anstyle", 81 + "once_cell_polyfill", 82 + "windows-sys 0.60.2", 83 + ] 84 + 85 + [[package]] 86 + name = "anyhow" 87 + version = "1.0.100" 88 + source = "registry+https://github.com/rust-lang/crates.io-index" 89 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 90 + 91 + [[package]] 92 + name = "arc-swap" 93 + version = "1.7.1" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 96 + 97 + [[package]] 98 + name = "arrayvec" 99 + version = "0.7.6" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 102 + 103 + [[package]] 104 + name = "async-compression" 105 + version = "0.4.31" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "9611ec0b6acea03372540509035db2f7f1e9f04da5d27728436fa994033c00a0" 108 + dependencies = [ 109 + "compression-codecs", 110 + "compression-core", 111 + "futures-core", 112 + "pin-project-lite", 113 + "tokio", 114 + ] 115 + 116 + [[package]] 117 + name = "async-trait" 118 + version = "0.1.89" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 121 + dependencies = [ 122 + "proc-macro2", 123 + "quote", 124 + "syn", 125 + ] 126 + 127 + [[package]] 128 + name = "atomic-waker" 129 + version = "1.1.2" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 132 + 133 + [[package]] 134 + name = "autocfg" 135 + version = "1.5.0" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 138 + 139 + [[package]] 140 + name = "aws-lc-rs" 141 + version = "1.14.1" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" 144 + dependencies = [ 145 + "aws-lc-sys", 146 + "zeroize", 147 + ] 148 + 149 + [[package]] 150 + name = "aws-lc-sys" 151 + version = "0.32.0" 152 + source = "registry+https://github.com/rust-lang/crates.io-index" 153 + checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" 154 + dependencies = [ 155 + "bindgen", 156 + "cc", 157 + "cmake", 158 + "dunce", 159 + "fs_extra", 160 + "libloading", 161 + ] 162 + 163 + [[package]] 164 + name = "axum" 165 + version = "0.8.4" 166 + source = "registry+https://github.com/rust-lang/crates.io-index" 167 + checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 168 + dependencies = [ 169 + "axum-core", 170 + "base64", 171 + "bytes", 172 + "form_urlencoded", 173 + "futures-util", 174 + "http", 175 + "http-body", 176 + "http-body-util", 177 + "hyper", 178 + "hyper-util", 179 + "itoa", 180 + "matchit", 181 + "memchr", 182 + "mime", 183 + "percent-encoding", 184 + "pin-project-lite", 185 + "rustversion", 186 + "serde", 187 + "serde_json", 188 + "serde_path_to_error", 189 + "serde_urlencoded", 190 + "sha1", 191 + "sync_wrapper", 192 + "tokio", 193 + "tokio-tungstenite", 194 + "tower", 195 + "tower-layer", 196 + "tower-service", 197 + "tracing", 198 + ] 199 + 200 + [[package]] 201 + name = "axum-core" 202 + version = "0.5.2" 203 + source = "registry+https://github.com/rust-lang/crates.io-index" 204 + checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 205 + dependencies = [ 206 + "bytes", 207 + "futures-core", 208 + "http", 209 + "http-body", 210 + "http-body-util", 211 + "mime", 212 + "pin-project-lite", 213 + "rustversion", 214 + "sync_wrapper", 215 + "tower-layer", 216 + "tower-service", 217 + "tracing", 218 + ] 219 + 220 + [[package]] 221 + name = "axum-extra" 222 + version = "0.10.1" 223 + source = "registry+https://github.com/rust-lang/crates.io-index" 224 + checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" 225 + dependencies = [ 226 + "axum", 227 + "axum-core", 228 + "bytes", 229 + "futures-util", 230 + "http", 231 + "http-body", 232 + "http-body-util", 233 + "mime", 234 + "pin-project-lite", 235 + "rustversion", 236 + "serde", 237 + "tokio", 238 + "tokio-util", 239 + "tower", 240 + "tower-layer", 241 + "tower-service", 242 + ] 243 + 244 + [[package]] 245 + name = "backtrace" 246 + version = "0.3.75" 247 + source = "registry+https://github.com/rust-lang/crates.io-index" 248 + checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 249 + dependencies = [ 250 + "addr2line", 251 + "cfg-if", 252 + "libc", 253 + "miniz_oxide", 254 + "object", 255 + "rustc-demangle", 256 + "windows-targets 0.52.6", 257 + ] 258 + 259 + [[package]] 260 + name = "base-x" 261 + version = "0.2.11" 262 + source = "registry+https://github.com/rust-lang/crates.io-index" 263 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 264 + 265 + [[package]] 266 + name = "base64" 267 + version = "0.22.1" 268 + source = "registry+https://github.com/rust-lang/crates.io-index" 269 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 270 + 271 + [[package]] 272 + name = "bindgen" 273 + version = "0.72.1" 274 + source = "registry+https://github.com/rust-lang/crates.io-index" 275 + checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" 276 + dependencies = [ 277 + "bitflags", 278 + "cexpr", 279 + "clang-sys", 280 + "itertools", 281 + "log", 282 + "prettyplease", 283 + "proc-macro2", 284 + "quote", 285 + "regex", 286 + "rustc-hash", 287 + "shlex", 288 + "syn", 289 + ] 290 + 291 + [[package]] 292 + name = "bitflags" 293 + version = "2.9.4" 294 + source = "registry+https://github.com/rust-lang/crates.io-index" 295 + checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 296 + 297 + [[package]] 298 + name = "block-buffer" 299 + version = "0.10.4" 300 + source = "registry+https://github.com/rust-lang/crates.io-index" 301 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 302 + dependencies = [ 303 + "generic-array", 304 + ] 305 + 306 + [[package]] 307 + name = "bstr" 308 + version = "1.12.0" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 311 + dependencies = [ 312 + "memchr", 313 + "regex-automata", 314 + "serde", 315 + ] 316 + 317 + [[package]] 318 + name = "bumpalo" 319 + version = "3.19.0" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 322 + 323 + [[package]] 324 + name = "byteorder" 325 + version = "1.5.0" 326 + source = "registry+https://github.com/rust-lang/crates.io-index" 327 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 328 + 329 + [[package]] 330 + name = "bytes" 331 + version = "1.10.1" 332 + source = "registry+https://github.com/rust-lang/crates.io-index" 333 + checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 334 + 335 + [[package]] 336 + name = "bytesize" 337 + version = "2.1.0" 338 + source = "registry+https://github.com/rust-lang/crates.io-index" 339 + checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" 340 + 341 + [[package]] 342 + name = "cc" 343 + version = "1.2.38" 344 + source = "registry+https://github.com/rust-lang/crates.io-index" 345 + checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" 346 + dependencies = [ 347 + "find-msvc-tools", 348 + "jobserver", 349 + "libc", 350 + "shlex", 351 + ] 352 + 353 + [[package]] 354 + name = "cexpr" 355 + version = "0.6.0" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 358 + dependencies = [ 359 + "nom", 360 + ] 361 + 362 + [[package]] 363 + name = "cfg-if" 364 + version = "1.0.3" 365 + source = "registry+https://github.com/rust-lang/crates.io-index" 366 + checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 367 + 368 + [[package]] 369 + name = "clang-sys" 370 + version = "1.8.1" 371 + source = "registry+https://github.com/rust-lang/crates.io-index" 372 + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 373 + dependencies = [ 374 + "glob", 375 + "libc", 376 + "libloading", 377 + ] 378 + 379 + [[package]] 380 + name = "clap" 381 + version = "4.5.48" 382 + source = "registry+https://github.com/rust-lang/crates.io-index" 383 + checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" 384 + dependencies = [ 385 + "clap_builder", 386 + "clap_derive", 387 + ] 388 + 389 + [[package]] 390 + name = "clap_builder" 391 + version = "4.5.48" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" 394 + dependencies = [ 395 + "anstream", 396 + "anstyle", 397 + "clap_lex", 398 + "strsim", 399 + ] 400 + 401 + [[package]] 402 + name = "clap_derive" 403 + version = "4.5.47" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" 406 + dependencies = [ 407 + "heck", 408 + "proc-macro2", 409 + "quote", 410 + "syn", 411 + ] 412 + 413 + [[package]] 414 + name = "clap_lex" 415 + version = "0.7.5" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 418 + 419 + [[package]] 420 + name = "clru" 421 + version = "0.6.2" 422 + source = "registry+https://github.com/rust-lang/crates.io-index" 423 + checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" 424 + 425 + [[package]] 426 + name = "cmake" 427 + version = "0.1.54" 428 + source = "registry+https://github.com/rust-lang/crates.io-index" 429 + checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 430 + dependencies = [ 431 + "cc", 432 + ] 433 + 434 + [[package]] 435 + name = "colorchoice" 436 + version = "1.0.4" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 439 + 440 + [[package]] 441 + name = "compression-codecs" 442 + version = "0.4.30" 443 + source = "registry+https://github.com/rust-lang/crates.io-index" 444 + checksum = "485abf41ac0c8047c07c87c72c8fb3eb5197f6e9d7ded615dfd1a00ae00a0f64" 445 + dependencies = [ 446 + "compression-core", 447 + "flate2", 448 + "memchr", 449 + ] 450 + 451 + [[package]] 452 + name = "compression-core" 453 + version = "0.4.29" 454 + source = "registry+https://github.com/rust-lang/crates.io-index" 455 + checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" 456 + 457 + [[package]] 458 + name = "core-foundation" 459 + version = "0.9.4" 460 + source = "registry+https://github.com/rust-lang/crates.io-index" 461 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 462 + dependencies = [ 463 + "core-foundation-sys", 464 + "libc", 465 + ] 466 + 467 + [[package]] 468 + name = "core-foundation-sys" 469 + version = "0.8.7" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 472 + 473 + [[package]] 474 + name = "cpufeatures" 475 + version = "0.2.17" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 478 + dependencies = [ 479 + "libc", 480 + ] 481 + 482 + [[package]] 483 + name = "crc32fast" 484 + version = "1.5.0" 485 + source = "registry+https://github.com/rust-lang/crates.io-index" 486 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 487 + dependencies = [ 488 + "cfg-if", 489 + ] 490 + 491 + [[package]] 492 + name = "critical-section" 493 + version = "1.2.0" 494 + source = "registry+https://github.com/rust-lang/crates.io-index" 495 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 496 + 497 + [[package]] 498 + name = "crossbeam-channel" 499 + version = "0.5.15" 500 + source = "registry+https://github.com/rust-lang/crates.io-index" 501 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 502 + dependencies = [ 503 + "crossbeam-utils", 504 + ] 505 + 506 + [[package]] 507 + name = "crossbeam-epoch" 508 + version = "0.9.18" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 511 + dependencies = [ 512 + "crossbeam-utils", 513 + ] 514 + 515 + [[package]] 516 + name = "crossbeam-utils" 517 + version = "0.8.21" 518 + source = "registry+https://github.com/rust-lang/crates.io-index" 519 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 520 + 521 + [[package]] 522 + name = "crypto-common" 523 + version = "0.1.6" 524 + source = "registry+https://github.com/rust-lang/crates.io-index" 525 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 526 + dependencies = [ 527 + "generic-array", 528 + "typenum", 529 + ] 530 + 531 + [[package]] 532 + name = "dashmap" 533 + version = "6.1.0" 534 + source = "registry+https://github.com/rust-lang/crates.io-index" 535 + checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 536 + dependencies = [ 537 + "cfg-if", 538 + "crossbeam-utils", 539 + "hashbrown 0.14.5", 540 + "lock_api", 541 + "once_cell", 542 + "parking_lot_core", 543 + ] 544 + 545 + [[package]] 546 + name = "data-encoding" 547 + version = "2.9.0" 548 + source = "registry+https://github.com/rust-lang/crates.io-index" 549 + checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 550 + 551 + [[package]] 552 + name = "data-encoding-macro" 553 + version = "0.1.18" 554 + source = "registry+https://github.com/rust-lang/crates.io-index" 555 + checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" 556 + dependencies = [ 557 + "data-encoding", 558 + "data-encoding-macro-internal", 559 + ] 560 + 561 + [[package]] 562 + name = "data-encoding-macro-internal" 563 + version = "0.1.16" 564 + source = "registry+https://github.com/rust-lang/crates.io-index" 565 + checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 566 + dependencies = [ 567 + "data-encoding", 568 + "syn", 569 + ] 570 + 571 + [[package]] 572 + name = "deranged" 573 + version = "0.5.4" 574 + source = "registry+https://github.com/rust-lang/crates.io-index" 575 + checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 576 + dependencies = [ 577 + "powerfmt", 578 + "serde_core", 579 + ] 580 + 581 + [[package]] 582 + name = "digest" 583 + version = "0.10.7" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 586 + dependencies = [ 587 + "block-buffer", 588 + "crypto-common", 589 + ] 590 + 591 + [[package]] 592 + name = "displaydoc" 593 + version = "0.2.5" 594 + source = "registry+https://github.com/rust-lang/crates.io-index" 595 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 596 + dependencies = [ 597 + "proc-macro2", 598 + "quote", 599 + "syn", 600 + ] 601 + 602 + [[package]] 603 + name = "dunce" 604 + version = "1.0.5" 605 + source = "registry+https://github.com/rust-lang/crates.io-index" 606 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 607 + 608 + [[package]] 609 + name = "either" 610 + version = "1.15.0" 611 + source = "registry+https://github.com/rust-lang/crates.io-index" 612 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 613 + 614 + [[package]] 615 + name = "encoding_rs" 616 + version = "0.8.35" 617 + source = "registry+https://github.com/rust-lang/crates.io-index" 618 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 619 + dependencies = [ 620 + "cfg-if", 621 + ] 622 + 623 + [[package]] 624 + name = "enum-as-inner" 625 + version = "0.6.1" 626 + source = "registry+https://github.com/rust-lang/crates.io-index" 627 + checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 628 + dependencies = [ 629 + "heck", 630 + "proc-macro2", 631 + "quote", 632 + "syn", 633 + ] 634 + 635 + [[package]] 636 + name = "equivalent" 637 + version = "1.0.2" 638 + source = "registry+https://github.com/rust-lang/crates.io-index" 639 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 640 + 641 + [[package]] 642 + name = "errno" 643 + version = "0.3.14" 644 + source = "registry+https://github.com/rust-lang/crates.io-index" 645 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 646 + dependencies = [ 647 + "libc", 648 + "windows-sys 0.61.0", 649 + ] 650 + 651 + [[package]] 652 + name = "faster-hex" 653 + version = "0.10.0" 654 + source = "registry+https://github.com/rust-lang/crates.io-index" 655 + checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" 656 + dependencies = [ 657 + "heapless", 658 + "serde", 659 + ] 660 + 661 + [[package]] 662 + name = "fastrand" 663 + version = "2.3.0" 664 + source = "registry+https://github.com/rust-lang/crates.io-index" 665 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 666 + 667 + [[package]] 668 + name = "filetime" 669 + version = "0.2.26" 670 + source = "registry+https://github.com/rust-lang/crates.io-index" 671 + checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" 672 + dependencies = [ 673 + "cfg-if", 674 + "libc", 675 + "libredox", 676 + "windows-sys 0.60.2", 677 + ] 678 + 679 + [[package]] 680 + name = "find-msvc-tools" 681 + version = "0.1.2" 682 + source = "registry+https://github.com/rust-lang/crates.io-index" 683 + checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" 684 + 685 + [[package]] 686 + name = "flate2" 687 + version = "1.1.2" 688 + source = "registry+https://github.com/rust-lang/crates.io-index" 689 + checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" 690 + dependencies = [ 691 + "crc32fast", 692 + "libz-rs-sys", 693 + "miniz_oxide", 694 + ] 695 + 696 + [[package]] 697 + name = "fnv" 698 + version = "1.0.7" 699 + source = "registry+https://github.com/rust-lang/crates.io-index" 700 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 701 + 702 + [[package]] 703 + name = "foldhash" 704 + version = "0.1.5" 705 + source = "registry+https://github.com/rust-lang/crates.io-index" 706 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 707 + 708 + [[package]] 709 + name = "foreign-types" 710 + version = "0.3.2" 711 + source = "registry+https://github.com/rust-lang/crates.io-index" 712 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 713 + dependencies = [ 714 + "foreign-types-shared", 715 + ] 716 + 717 + [[package]] 718 + name = "foreign-types-shared" 719 + version = "0.1.1" 720 + source = "registry+https://github.com/rust-lang/crates.io-index" 721 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 722 + 723 + [[package]] 724 + name = "form_urlencoded" 725 + version = "1.2.2" 726 + source = "registry+https://github.com/rust-lang/crates.io-index" 727 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 728 + dependencies = [ 729 + "percent-encoding", 730 + ] 731 + 732 + [[package]] 733 + name = "fs_extra" 734 + version = "1.3.0" 735 + source = "registry+https://github.com/rust-lang/crates.io-index" 736 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 737 + 738 + [[package]] 739 + name = "futures-channel" 740 + version = "0.3.31" 741 + source = "registry+https://github.com/rust-lang/crates.io-index" 742 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 743 + dependencies = [ 744 + "futures-core", 745 + ] 746 + 747 + [[package]] 748 + name = "futures-core" 749 + version = "0.3.31" 750 + source = "registry+https://github.com/rust-lang/crates.io-index" 751 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 752 + 753 + [[package]] 754 + name = "futures-io" 755 + version = "0.3.31" 756 + source = "registry+https://github.com/rust-lang/crates.io-index" 757 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 758 + 759 + [[package]] 760 + name = "futures-sink" 761 + version = "0.3.31" 762 + source = "registry+https://github.com/rust-lang/crates.io-index" 763 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 764 + 765 + [[package]] 766 + name = "futures-task" 767 + version = "0.3.31" 768 + source = "registry+https://github.com/rust-lang/crates.io-index" 769 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 770 + 771 + [[package]] 772 + name = "futures-util" 773 + version = "0.3.31" 774 + source = "registry+https://github.com/rust-lang/crates.io-index" 775 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 776 + dependencies = [ 777 + "futures-core", 778 + "futures-sink", 779 + "futures-task", 780 + "pin-project-lite", 781 + "pin-utils", 782 + "slab", 783 + ] 784 + 785 + [[package]] 786 + name = "generic-array" 787 + version = "0.14.7" 788 + source = "registry+https://github.com/rust-lang/crates.io-index" 789 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 790 + dependencies = [ 791 + "typenum", 792 + "version_check", 793 + ] 794 + 795 + [[package]] 796 + name = "getrandom" 797 + version = "0.2.16" 798 + source = "registry+https://github.com/rust-lang/crates.io-index" 799 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 800 + dependencies = [ 801 + "cfg-if", 802 + "libc", 803 + "wasi 0.11.1+wasi-snapshot-preview1", 804 + ] 805 + 806 + [[package]] 807 + name = "getrandom" 808 + version = "0.3.3" 809 + source = "registry+https://github.com/rust-lang/crates.io-index" 810 + checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 811 + dependencies = [ 812 + "cfg-if", 813 + "libc", 814 + "r-efi", 815 + "wasi 0.14.7+wasi-0.2.4", 816 + ] 817 + 818 + [[package]] 819 + name = "gimli" 820 + version = "0.31.1" 821 + source = "registry+https://github.com/rust-lang/crates.io-index" 822 + checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 823 + 824 + [[package]] 825 + name = "gix" 826 + version = "0.73.0" 827 + source = "registry+https://github.com/rust-lang/crates.io-index" 828 + checksum = "514c29cc879bdc0286b0cbc205585a49b252809eb86c69df4ce4f855ee75f635" 829 + dependencies = [ 830 + "gix-actor", 831 + "gix-archive", 832 + "gix-attributes", 833 + "gix-command", 834 + "gix-commitgraph", 835 + "gix-config", 836 + "gix-credentials", 837 + "gix-date", 838 + "gix-diff", 839 + "gix-dir", 840 + "gix-discover", 841 + "gix-features", 842 + "gix-filter", 843 + "gix-fs", 844 + "gix-glob", 845 + "gix-hash", 846 + "gix-hashtable", 847 + "gix-ignore", 848 + "gix-index", 849 + "gix-lock", 850 + "gix-mailmap", 851 + "gix-negotiate", 852 + "gix-object", 853 + "gix-odb", 854 + "gix-pack", 855 + "gix-path", 856 + "gix-pathspec", 857 + "gix-prompt", 858 + "gix-protocol", 859 + "gix-ref", 860 + "gix-refspec", 861 + "gix-revision", 862 + "gix-revwalk", 863 + "gix-sec", 864 + "gix-shallow", 865 + "gix-status", 866 + "gix-submodule", 867 + "gix-tempfile", 868 + "gix-trace", 869 + "gix-traverse", 870 + "gix-url", 871 + "gix-utils", 872 + "gix-validate", 873 + "gix-worktree", 874 + "gix-worktree-state", 875 + "gix-worktree-stream", 876 + "once_cell", 877 + "parking_lot", 878 + "regex", 879 + "signal-hook", 880 + "smallvec", 881 + "thiserror", 882 + ] 883 + 884 + [[package]] 885 + name = "gix-actor" 886 + version = "0.35.4" 887 + source = "registry+https://github.com/rust-lang/crates.io-index" 888 + checksum = "2d36dcf9efe32b51b12dfa33cedff8414926124e760a32f9e7a6b5580d280967" 889 + dependencies = [ 890 + "bstr", 891 + "gix-date", 892 + "gix-utils", 893 + "itoa", 894 + "thiserror", 895 + "winnow", 896 + ] 897 + 898 + [[package]] 899 + name = "gix-archive" 900 + version = "0.22.0" 901 + source = "registry+https://github.com/rust-lang/crates.io-index" 902 + checksum = "7be088a0e1b30abe15572ffafb3409172a3d88148e13959734f24f52112a19d6" 903 + dependencies = [ 904 + "bstr", 905 + "gix-date", 906 + "gix-object", 907 + "gix-worktree-stream", 908 + "jiff", 909 + "thiserror", 910 + ] 911 + 912 + [[package]] 913 + name = "gix-attributes" 914 + version = "0.27.0" 915 + source = "registry+https://github.com/rust-lang/crates.io-index" 916 + checksum = "45442188216d08a5959af195f659cb1f244a50d7d2d0c3873633b1cd7135f638" 917 + dependencies = [ 918 + "bstr", 919 + "gix-glob", 920 + "gix-path", 921 + "gix-quote", 922 + "gix-trace", 923 + "kstring", 924 + "smallvec", 925 + "thiserror", 926 + "unicode-bom", 927 + ] 928 + 929 + [[package]] 930 + name = "gix-bitmap" 931 + version = "0.2.14" 932 + source = "registry+https://github.com/rust-lang/crates.io-index" 933 + checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" 934 + dependencies = [ 935 + "thiserror", 936 + ] 937 + 938 + [[package]] 939 + name = "gix-chunk" 940 + version = "0.4.11" 941 + source = "registry+https://github.com/rust-lang/crates.io-index" 942 + checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" 943 + dependencies = [ 944 + "thiserror", 945 + ] 946 + 947 + [[package]] 948 + name = "gix-command" 949 + version = "0.6.2" 950 + source = "registry+https://github.com/rust-lang/crates.io-index" 951 + checksum = "6b31b65ca48a352ae86312b27a514a0c661935f96b481ac8b4371f65815eb196" 952 + dependencies = [ 953 + "bstr", 954 + "gix-path", 955 + "gix-quote", 956 + "gix-trace", 957 + "shell-words", 958 + ] 959 + 960 + [[package]] 961 + name = "gix-commitgraph" 962 + version = "0.29.0" 963 + source = "registry+https://github.com/rust-lang/crates.io-index" 964 + checksum = "6bb23121e952f43a5b07e3e80890336cb847297467a410475036242732980d06" 965 + dependencies = [ 966 + "bstr", 967 + "gix-chunk", 968 + "gix-hash", 969 + "memmap2", 970 + "thiserror", 971 + ] 972 + 973 + [[package]] 974 + name = "gix-config" 975 + version = "0.46.0" 976 + source = "registry+https://github.com/rust-lang/crates.io-index" 977 + checksum = "5dfb898c5b695fd4acfc3c0ab638525a65545d47706064dcf7b5ead6cdb136c0" 978 + dependencies = [ 979 + "bstr", 980 + "gix-config-value", 981 + "gix-features", 982 + "gix-glob", 983 + "gix-path", 984 + "gix-ref", 985 + "gix-sec", 986 + "memchr", 987 + "once_cell", 988 + "smallvec", 989 + "thiserror", 990 + "unicode-bom", 991 + "winnow", 992 + ] 993 + 994 + [[package]] 995 + name = "gix-config-value" 996 + version = "0.15.1" 997 + source = "registry+https://github.com/rust-lang/crates.io-index" 998 + checksum = "9f012703eb67e263c6c1fc96649fec47694dd3e5d2a91abfc65e4a6a6dc85309" 999 + dependencies = [ 1000 + "bitflags", 1001 + "bstr", 1002 + "gix-path", 1003 + "libc", 1004 + "thiserror", 1005 + ] 1006 + 1007 + [[package]] 1008 + name = "gix-credentials" 1009 + version = "0.30.0" 1010 + source = "registry+https://github.com/rust-lang/crates.io-index" 1011 + checksum = "0039dd3ac606dd80b16353a41b61fc237ca5cb8b612f67a9f880adfad4be4e05" 1012 + dependencies = [ 1013 + "bstr", 1014 + "gix-command", 1015 + "gix-config-value", 1016 + "gix-date", 1017 + "gix-path", 1018 + "gix-prompt", 1019 + "gix-sec", 1020 + "gix-trace", 1021 + "gix-url", 1022 + "thiserror", 1023 + ] 1024 + 1025 + [[package]] 1026 + name = "gix-date" 1027 + version = "0.10.5" 1028 + source = "registry+https://github.com/rust-lang/crates.io-index" 1029 + checksum = "996b6b90bafb287330af92b274c3e64309dc78359221d8612d11cd10c8b9fe1c" 1030 + dependencies = [ 1031 + "bstr", 1032 + "itoa", 1033 + "jiff", 1034 + "smallvec", 1035 + "thiserror", 1036 + ] 1037 + 1038 + [[package]] 1039 + name = "gix-diff" 1040 + version = "0.53.0" 1041 + source = "registry+https://github.com/rust-lang/crates.io-index" 1042 + checksum = "de854852010d44a317f30c92d67a983e691c9478c8a3fb4117c1f48626bcdea8" 1043 + dependencies = [ 1044 + "bstr", 1045 + "gix-attributes", 1046 + "gix-command", 1047 + "gix-filter", 1048 + "gix-fs", 1049 + "gix-hash", 1050 + "gix-index", 1051 + "gix-object", 1052 + "gix-path", 1053 + "gix-pathspec", 1054 + "gix-tempfile", 1055 + "gix-trace", 1056 + "gix-traverse", 1057 + "gix-worktree", 1058 + "imara-diff", 1059 + "thiserror", 1060 + ] 1061 + 1062 + [[package]] 1063 + name = "gix-dir" 1064 + version = "0.15.0" 1065 + source = "registry+https://github.com/rust-lang/crates.io-index" 1066 + checksum = "dad34e4f373f94902df1ba1d2a1df3a1b29eacd15e316ac5972d842e31422dd7" 1067 + dependencies = [ 1068 + "bstr", 1069 + "gix-discover", 1070 + "gix-fs", 1071 + "gix-ignore", 1072 + "gix-index", 1073 + "gix-object", 1074 + "gix-path", 1075 + "gix-pathspec", 1076 + "gix-trace", 1077 + "gix-utils", 1078 + "gix-worktree", 1079 + "thiserror", 1080 + ] 1081 + 1082 + [[package]] 1083 + name = "gix-discover" 1084 + version = "0.41.0" 1085 + source = "registry+https://github.com/rust-lang/crates.io-index" 1086 + checksum = "ffb180c91ca1a2cf53e828bb63d8d8f8fa7526f49b83b33d7f46cbeb5d79d30a" 1087 + dependencies = [ 1088 + "bstr", 1089 + "dunce", 1090 + "gix-fs", 1091 + "gix-hash", 1092 + "gix-path", 1093 + "gix-ref", 1094 + "gix-sec", 1095 + "thiserror", 1096 + ] 1097 + 1098 + [[package]] 1099 + name = "gix-features" 1100 + version = "0.43.1" 1101 + source = "registry+https://github.com/rust-lang/crates.io-index" 1102 + checksum = "cd1543cd9b8abcbcebaa1a666a5c168ee2cda4dea50d3961ee0e6d1c42f81e5b" 1103 + dependencies = [ 1104 + "bytes", 1105 + "bytesize", 1106 + "crc32fast", 1107 + "crossbeam-channel", 1108 + "flate2", 1109 + "gix-path", 1110 + "gix-trace", 1111 + "gix-utils", 1112 + "libc", 1113 + "once_cell", 1114 + "parking_lot", 1115 + "prodash", 1116 + "thiserror", 1117 + "walkdir", 1118 + ] 1119 + 1120 + [[package]] 1121 + name = "gix-filter" 1122 + version = "0.20.0" 1123 + source = "registry+https://github.com/rust-lang/crates.io-index" 1124 + checksum = "aa6571a3927e7ab10f64279a088e0dae08e8da05547771796d7389bbe28ad9ff" 1125 + dependencies = [ 1126 + "bstr", 1127 + "encoding_rs", 1128 + "gix-attributes", 1129 + "gix-command", 1130 + "gix-hash", 1131 + "gix-object", 1132 + "gix-packetline-blocking", 1133 + "gix-path", 1134 + "gix-quote", 1135 + "gix-trace", 1136 + "gix-utils", 1137 + "smallvec", 1138 + "thiserror", 1139 + ] 1140 + 1141 + [[package]] 1142 + name = "gix-fs" 1143 + version = "0.16.1" 1144 + source = "registry+https://github.com/rust-lang/crates.io-index" 1145 + checksum = "9a4d90307d064fa7230e0f87b03231be28f8ba63b913fc15346f489519d0c304" 1146 + dependencies = [ 1147 + "bstr", 1148 + "fastrand", 1149 + "gix-features", 1150 + "gix-path", 1151 + "gix-utils", 1152 + "thiserror", 1153 + ] 1154 + 1155 + [[package]] 1156 + name = "gix-glob" 1157 + version = "0.21.0" 1158 + source = "registry+https://github.com/rust-lang/crates.io-index" 1159 + checksum = "b947db8366823e7a750c254f6bb29e27e17f27e457bf336ba79b32423db62cd5" 1160 + dependencies = [ 1161 + "bitflags", 1162 + "bstr", 1163 + "gix-features", 1164 + "gix-path", 1165 + ] 1166 + 1167 + [[package]] 1168 + name = "gix-hash" 1169 + version = "0.19.0" 1170 + source = "registry+https://github.com/rust-lang/crates.io-index" 1171 + checksum = "251fad79796a731a2a7664d9ea95ee29a9e99474de2769e152238d4fdb69d50e" 1172 + dependencies = [ 1173 + "faster-hex", 1174 + "gix-features", 1175 + "sha1-checked", 1176 + "thiserror", 1177 + ] 1178 + 1179 + [[package]] 1180 + name = "gix-hashtable" 1181 + version = "0.9.0" 1182 + source = "registry+https://github.com/rust-lang/crates.io-index" 1183 + checksum = "c35300b54896153e55d53f4180460931ccd69b7e8d2f6b9d6401122cdedc4f07" 1184 + dependencies = [ 1185 + "gix-hash", 1186 + "hashbrown 0.15.5", 1187 + "parking_lot", 1188 + ] 1189 + 1190 + [[package]] 1191 + name = "gix-ignore" 1192 + version = "0.16.0" 1193 + source = "registry+https://github.com/rust-lang/crates.io-index" 1194 + checksum = "564d6fddf46e2c981f571b23d6ad40cb08bddcaf6fc7458b1d49727ad23c2870" 1195 + dependencies = [ 1196 + "bstr", 1197 + "gix-glob", 1198 + "gix-path", 1199 + "gix-trace", 1200 + "unicode-bom", 1201 + ] 1202 + 1203 + [[package]] 1204 + name = "gix-index" 1205 + version = "0.41.0" 1206 + source = "registry+https://github.com/rust-lang/crates.io-index" 1207 + checksum = "2af39fde3ce4ce11371d9ce826f2936ec347318f2d1972fe98c2e7134e267e25" 1208 + dependencies = [ 1209 + "bitflags", 1210 + "bstr", 1211 + "filetime", 1212 + "fnv", 1213 + "gix-bitmap", 1214 + "gix-features", 1215 + "gix-fs", 1216 + "gix-hash", 1217 + "gix-lock", 1218 + "gix-object", 1219 + "gix-traverse", 1220 + "gix-utils", 1221 + "gix-validate", 1222 + "hashbrown 0.15.5", 1223 + "itoa", 1224 + "libc", 1225 + "memmap2", 1226 + "rustix", 1227 + "smallvec", 1228 + "thiserror", 1229 + ] 1230 + 1231 + [[package]] 1232 + name = "gix-lock" 1233 + version = "18.0.0" 1234 + source = "registry+https://github.com/rust-lang/crates.io-index" 1235 + checksum = "b9fa71da90365668a621e184eb5b979904471af1b3b09b943a84bc50e8ad42ed" 1236 + dependencies = [ 1237 + "gix-tempfile", 1238 + "gix-utils", 1239 + "thiserror", 1240 + ] 1241 + 1242 + [[package]] 1243 + name = "gix-mailmap" 1244 + version = "0.27.2" 1245 + source = "registry+https://github.com/rust-lang/crates.io-index" 1246 + checksum = "9a8982e1874a2034d7dd481bcdd6a05579ba444bcda748511eb0f8e50eb10487" 1247 + dependencies = [ 1248 + "bstr", 1249 + "gix-actor", 1250 + "gix-date", 1251 + "thiserror", 1252 + ] 1253 + 1254 + [[package]] 1255 + name = "gix-negotiate" 1256 + version = "0.21.0" 1257 + source = "registry+https://github.com/rust-lang/crates.io-index" 1258 + checksum = "1d58d4c9118885233be971e0d7a589f5cfb1a8bd6cb6e2ecfb0fc6b1b293c83b" 1259 + dependencies = [ 1260 + "bitflags", 1261 + "gix-commitgraph", 1262 + "gix-date", 1263 + "gix-hash", 1264 + "gix-object", 1265 + "gix-revwalk", 1266 + "smallvec", 1267 + "thiserror", 1268 + ] 1269 + 1270 + [[package]] 1271 + name = "gix-object" 1272 + version = "0.50.2" 1273 + source = "registry+https://github.com/rust-lang/crates.io-index" 1274 + checksum = "d69ce108ab67b65fbd4fb7e1331502429d78baeb2eee10008bdef55765397c07" 1275 + dependencies = [ 1276 + "bstr", 1277 + "gix-actor", 1278 + "gix-date", 1279 + "gix-features", 1280 + "gix-hash", 1281 + "gix-hashtable", 1282 + "gix-path", 1283 + "gix-utils", 1284 + "gix-validate", 1285 + "itoa", 1286 + "smallvec", 1287 + "thiserror", 1288 + "winnow", 1289 + ] 1290 + 1291 + [[package]] 1292 + name = "gix-odb" 1293 + version = "0.70.0" 1294 + source = "registry+https://github.com/rust-lang/crates.io-index" 1295 + checksum = "9c9d7af10fda9df0bb4f7f9bd507963560b3c66cb15a5b825caf752e0eb109ac" 1296 + dependencies = [ 1297 + "arc-swap", 1298 + "gix-date", 1299 + "gix-features", 1300 + "gix-fs", 1301 + "gix-hash", 1302 + "gix-hashtable", 1303 + "gix-object", 1304 + "gix-pack", 1305 + "gix-path", 1306 + "gix-quote", 1307 + "parking_lot", 1308 + "tempfile", 1309 + "thiserror", 1310 + ] 1311 + 1312 + [[package]] 1313 + name = "gix-pack" 1314 + version = "0.60.0" 1315 + source = "registry+https://github.com/rust-lang/crates.io-index" 1316 + checksum = "d8571df89bfca5abb49c3e3372393f7af7e6f8b8dbe2b96303593cef5b263019" 1317 + dependencies = [ 1318 + "clru", 1319 + "gix-chunk", 1320 + "gix-features", 1321 + "gix-hash", 1322 + "gix-hashtable", 1323 + "gix-object", 1324 + "gix-path", 1325 + "memmap2", 1326 + "smallvec", 1327 + "thiserror", 1328 + "uluru", 1329 + ] 1330 + 1331 + [[package]] 1332 + name = "gix-packetline" 1333 + version = "0.19.1" 1334 + source = "registry+https://github.com/rust-lang/crates.io-index" 1335 + checksum = "2592fbd36249a2fea11056f7055cc376301ef38d903d157de41998335bbf1f93" 1336 + dependencies = [ 1337 + "bstr", 1338 + "faster-hex", 1339 + "gix-trace", 1340 + "thiserror", 1341 + ] 1342 + 1343 + [[package]] 1344 + name = "gix-packetline-blocking" 1345 + version = "0.19.1" 1346 + source = "registry+https://github.com/rust-lang/crates.io-index" 1347 + checksum = "fc4e706f328cd494cc8f932172e123a72b9a4711b0db5e411681432a89bd4c94" 1348 + dependencies = [ 1349 + "bstr", 1350 + "faster-hex", 1351 + "gix-trace", 1352 + "thiserror", 1353 + ] 1354 + 1355 + [[package]] 1356 + name = "gix-path" 1357 + version = "0.10.20" 1358 + source = "registry+https://github.com/rust-lang/crates.io-index" 1359 + checksum = "06d37034a4c67bbdda76f7bcd037b2f7bc0fba0c09a6662b19697a5716e7b2fd" 1360 + dependencies = [ 1361 + "bstr", 1362 + "gix-trace", 1363 + "gix-validate", 1364 + "home", 1365 + "once_cell", 1366 + "thiserror", 1367 + ] 1368 + 1369 + [[package]] 1370 + name = "gix-pathspec" 1371 + version = "0.12.0" 1372 + source = "registry+https://github.com/rust-lang/crates.io-index" 1373 + checksum = "daedead611c9bd1f3640dc90a9012b45f790201788af4d659f28d94071da7fba" 1374 + dependencies = [ 1375 + "bitflags", 1376 + "bstr", 1377 + "gix-attributes", 1378 + "gix-config-value", 1379 + "gix-glob", 1380 + "gix-path", 1381 + "thiserror", 1382 + ] 1383 + 1384 + [[package]] 1385 + name = "gix-prompt" 1386 + version = "0.11.1" 1387 + source = "registry+https://github.com/rust-lang/crates.io-index" 1388 + checksum = "6ffa1a7a34c81710aaa666a428c142b6c5d640492fcd41267db0740d923c7906" 1389 + dependencies = [ 1390 + "gix-command", 1391 + "gix-config-value", 1392 + "parking_lot", 1393 + "rustix", 1394 + "thiserror", 1395 + ] 1396 + 1397 + [[package]] 1398 + name = "gix-protocol" 1399 + version = "0.51.0" 1400 + source = "registry+https://github.com/rust-lang/crates.io-index" 1401 + checksum = "12b4b807c47ffcf7c1e5b8119585368a56449f3493da93b931e1d4239364e922" 1402 + dependencies = [ 1403 + "bstr", 1404 + "gix-date", 1405 + "gix-features", 1406 + "gix-hash", 1407 + "gix-ref", 1408 + "gix-shallow", 1409 + "gix-transport", 1410 + "gix-utils", 1411 + "maybe-async", 1412 + "thiserror", 1413 + "winnow", 1414 + ] 1415 + 1416 + [[package]] 1417 + name = "gix-quote" 1418 + version = "0.6.0" 1419 + source = "registry+https://github.com/rust-lang/crates.io-index" 1420 + checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd" 1421 + dependencies = [ 1422 + "bstr", 1423 + "gix-utils", 1424 + "thiserror", 1425 + ] 1426 + 1427 + [[package]] 1428 + name = "gix-ref" 1429 + version = "0.53.1" 1430 + source = "registry+https://github.com/rust-lang/crates.io-index" 1431 + checksum = "b966f578079a42f4a51413b17bce476544cca1cf605753466669082f94721758" 1432 + dependencies = [ 1433 + "gix-actor", 1434 + "gix-features", 1435 + "gix-fs", 1436 + "gix-hash", 1437 + "gix-lock", 1438 + "gix-object", 1439 + "gix-path", 1440 + "gix-tempfile", 1441 + "gix-utils", 1442 + "gix-validate", 1443 + "memmap2", 1444 + "thiserror", 1445 + "winnow", 1446 + ] 1447 + 1448 + [[package]] 1449 + name = "gix-refspec" 1450 + version = "0.31.0" 1451 + source = "registry+https://github.com/rust-lang/crates.io-index" 1452 + checksum = "7d29cae1ae31108826e7156a5e60bffacab405f4413f5bc0375e19772cce0055" 1453 + dependencies = [ 1454 + "bstr", 1455 + "gix-hash", 1456 + "gix-revision", 1457 + "gix-validate", 1458 + "smallvec", 1459 + "thiserror", 1460 + ] 1461 + 1462 + [[package]] 1463 + name = "gix-revision" 1464 + version = "0.35.0" 1465 + source = "registry+https://github.com/rust-lang/crates.io-index" 1466 + checksum = "f651f2b1742f760bb8161d6743229206e962b73d9c33c41f4e4aefa6586cbd3d" 1467 + dependencies = [ 1468 + "bitflags", 1469 + "bstr", 1470 + "gix-commitgraph", 1471 + "gix-date", 1472 + "gix-hash", 1473 + "gix-hashtable", 1474 + "gix-object", 1475 + "gix-revwalk", 1476 + "gix-trace", 1477 + "thiserror", 1478 + ] 1479 + 1480 + [[package]] 1481 + name = "gix-revwalk" 1482 + version = "0.21.0" 1483 + source = "registry+https://github.com/rust-lang/crates.io-index" 1484 + checksum = "06e74f91709729e099af6721bd0fa7d62f243f2005085152301ca5cdd86ec02c" 1485 + dependencies = [ 1486 + "gix-commitgraph", 1487 + "gix-date", 1488 + "gix-hash", 1489 + "gix-hashtable", 1490 + "gix-object", 1491 + "smallvec", 1492 + "thiserror", 1493 + ] 1494 + 1495 + [[package]] 1496 + name = "gix-sec" 1497 + version = "0.12.0" 1498 + source = "registry+https://github.com/rust-lang/crates.io-index" 1499 + checksum = "09f7053ed7c66633b56c57bc6ed3377be3166eaf3dc2df9f1c5ec446df6fdf2c" 1500 + dependencies = [ 1501 + "bitflags", 1502 + "gix-path", 1503 + "libc", 1504 + "windows-sys 0.59.0", 1505 + ] 1506 + 1507 + [[package]] 1508 + name = "gix-shallow" 1509 + version = "0.5.0" 1510 + source = "registry+https://github.com/rust-lang/crates.io-index" 1511 + checksum = "d936745103243ae4c510f19e0760ce73fb0f08096588fdbe0f0d7fb7ce8944b7" 1512 + dependencies = [ 1513 + "bstr", 1514 + "gix-hash", 1515 + "gix-lock", 1516 + "thiserror", 1517 + ] 1518 + 1519 + [[package]] 1520 + name = "gix-status" 1521 + version = "0.20.0" 1522 + source = "registry+https://github.com/rust-lang/crates.io-index" 1523 + checksum = "2a4afff9b34eeececa8bdc32b42fb318434b6b1391d9f8d45fe455af08dc2d35" 1524 + dependencies = [ 1525 + "bstr", 1526 + "filetime", 1527 + "gix-diff", 1528 + "gix-dir", 1529 + "gix-features", 1530 + "gix-filter", 1531 + "gix-fs", 1532 + "gix-hash", 1533 + "gix-index", 1534 + "gix-object", 1535 + "gix-path", 1536 + "gix-pathspec", 1537 + "gix-worktree", 1538 + "portable-atomic", 1539 + "thiserror", 1540 + ] 1541 + 1542 + [[package]] 1543 + name = "gix-submodule" 1544 + version = "0.20.0" 1545 + source = "registry+https://github.com/rust-lang/crates.io-index" 1546 + checksum = "657cc5dd43cbc7a14d9c5aaf02cfbe9c2a15d077cded3f304adb30ef78852d3e" 1547 + dependencies = [ 1548 + "bstr", 1549 + "gix-config", 1550 + "gix-path", 1551 + "gix-pathspec", 1552 + "gix-refspec", 1553 + "gix-url", 1554 + "thiserror", 1555 + ] 1556 + 1557 + [[package]] 1558 + name = "gix-tempfile" 1559 + version = "18.0.0" 1560 + source = "registry+https://github.com/rust-lang/crates.io-index" 1561 + checksum = "666c0041bcdedf5fa05e9bef663c897debab24b7dc1741605742412d1d47da57" 1562 + dependencies = [ 1563 + "dashmap", 1564 + "gix-fs", 1565 + "libc", 1566 + "once_cell", 1567 + "parking_lot", 1568 + "signal-hook", 1569 + "signal-hook-registry", 1570 + "tempfile", 1571 + ] 1572 + 1573 + [[package]] 1574 + name = "gix-trace" 1575 + version = "0.1.13" 1576 + source = "registry+https://github.com/rust-lang/crates.io-index" 1577 + checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658" 1578 + 1579 + [[package]] 1580 + name = "gix-transport" 1581 + version = "0.48.0" 1582 + source = "registry+https://github.com/rust-lang/crates.io-index" 1583 + checksum = "12f7cc0179fc89d53c54e1f9ce51229494864ab4bf136132d69db1b011741ca3" 1584 + dependencies = [ 1585 + "bstr", 1586 + "gix-command", 1587 + "gix-features", 1588 + "gix-packetline", 1589 + "gix-quote", 1590 + "gix-sec", 1591 + "gix-url", 1592 + "thiserror", 1593 + ] 1594 + 1595 + [[package]] 1596 + name = "gix-traverse" 1597 + version = "0.47.0" 1598 + source = "registry+https://github.com/rust-lang/crates.io-index" 1599 + checksum = "c7cdc82509d792ba0ad815f86f6b469c7afe10f94362e96c4494525a6601bdd5" 1600 + dependencies = [ 1601 + "bitflags", 1602 + "gix-commitgraph", 1603 + "gix-date", 1604 + "gix-hash", 1605 + "gix-hashtable", 1606 + "gix-object", 1607 + "gix-revwalk", 1608 + "smallvec", 1609 + "thiserror", 1610 + ] 1611 + 1612 + [[package]] 1613 + name = "gix-url" 1614 + version = "0.32.0" 1615 + source = "registry+https://github.com/rust-lang/crates.io-index" 1616 + checksum = "1b76a9d266254ad287ffd44467cd88e7868799b08f4d52e02d942b93e514d16f" 1617 + dependencies = [ 1618 + "bstr", 1619 + "gix-features", 1620 + "gix-path", 1621 + "percent-encoding", 1622 + "thiserror", 1623 + "url", 1624 + ] 1625 + 1626 + [[package]] 1627 + name = "gix-utils" 1628 + version = "0.3.0" 1629 + source = "registry+https://github.com/rust-lang/crates.io-index" 1630 + checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" 1631 + dependencies = [ 1632 + "bstr", 1633 + "fastrand", 1634 + "unicode-normalization", 1635 + ] 1636 + 1637 + [[package]] 1638 + name = "gix-validate" 1639 + version = "0.10.0" 1640 + source = "registry+https://github.com/rust-lang/crates.io-index" 1641 + checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" 1642 + dependencies = [ 1643 + "bstr", 1644 + "thiserror", 1645 + ] 1646 + 1647 + [[package]] 1648 + name = "gix-worktree" 1649 + version = "0.42.0" 1650 + source = "registry+https://github.com/rust-lang/crates.io-index" 1651 + checksum = "55f625ac9126c19bef06dbc6d2703cdd7987e21e35b497bb265ac37d383877b1" 1652 + dependencies = [ 1653 + "bstr", 1654 + "gix-attributes", 1655 + "gix-features", 1656 + "gix-fs", 1657 + "gix-glob", 1658 + "gix-hash", 1659 + "gix-ignore", 1660 + "gix-index", 1661 + "gix-object", 1662 + "gix-path", 1663 + "gix-validate", 1664 + ] 1665 + 1666 + [[package]] 1667 + name = "gix-worktree-state" 1668 + version = "0.20.0" 1669 + source = "registry+https://github.com/rust-lang/crates.io-index" 1670 + checksum = "06ba9b17cbacc02b25801197b20100f7f9bd621db1e7fce9d3c8ab3175207bf8" 1671 + dependencies = [ 1672 + "bstr", 1673 + "gix-features", 1674 + "gix-filter", 1675 + "gix-fs", 1676 + "gix-glob", 1677 + "gix-hash", 1678 + "gix-index", 1679 + "gix-object", 1680 + "gix-path", 1681 + "gix-worktree", 1682 + "io-close", 1683 + "thiserror", 1684 + ] 1685 + 1686 + [[package]] 1687 + name = "gix-worktree-stream" 1688 + version = "0.22.0" 1689 + source = "registry+https://github.com/rust-lang/crates.io-index" 1690 + checksum = "f56a737cefbcd90b573cb5393d636f6dc5e0d08a8086356d8c4fcc623b49a0e8" 1691 + dependencies = [ 1692 + "gix-attributes", 1693 + "gix-features", 1694 + "gix-filter", 1695 + "gix-fs", 1696 + "gix-hash", 1697 + "gix-object", 1698 + "gix-path", 1699 + "gix-traverse", 1700 + "parking_lot", 1701 + "thiserror", 1702 + ] 1703 + 1704 + [[package]] 1705 + name = "glob" 1706 + version = "0.3.3" 1707 + source = "registry+https://github.com/rust-lang/crates.io-index" 1708 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1709 + 1710 + [[package]] 1711 + name = "h2" 1712 + version = "0.4.12" 1713 + source = "registry+https://github.com/rust-lang/crates.io-index" 1714 + checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 1715 + dependencies = [ 1716 + "atomic-waker", 1717 + "bytes", 1718 + "fnv", 1719 + "futures-core", 1720 + "futures-sink", 1721 + "http", 1722 + "indexmap", 1723 + "slab", 1724 + "tokio", 1725 + "tokio-util", 1726 + "tracing", 1727 + ] 1728 + 1729 + [[package]] 1730 + name = "hash32" 1731 + version = "0.3.1" 1732 + source = "registry+https://github.com/rust-lang/crates.io-index" 1733 + checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 1734 + dependencies = [ 1735 + "byteorder", 1736 + ] 1737 + 1738 + [[package]] 1739 + name = "hashbrown" 1740 + version = "0.14.5" 1741 + source = "registry+https://github.com/rust-lang/crates.io-index" 1742 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1743 + 1744 + [[package]] 1745 + name = "hashbrown" 1746 + version = "0.15.5" 1747 + source = "registry+https://github.com/rust-lang/crates.io-index" 1748 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1749 + dependencies = [ 1750 + "allocator-api2", 1751 + "equivalent", 1752 + "foldhash", 1753 + ] 1754 + 1755 + [[package]] 1756 + name = "hashbrown" 1757 + version = "0.16.0" 1758 + source = "registry+https://github.com/rust-lang/crates.io-index" 1759 + checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 1760 + 1761 + [[package]] 1762 + name = "heapless" 1763 + version = "0.8.0" 1764 + source = "registry+https://github.com/rust-lang/crates.io-index" 1765 + checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 1766 + dependencies = [ 1767 + "hash32", 1768 + "stable_deref_trait", 1769 + ] 1770 + 1771 + [[package]] 1772 + name = "heck" 1773 + version = "0.5.0" 1774 + source = "registry+https://github.com/rust-lang/crates.io-index" 1775 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1776 + 1777 + [[package]] 1778 + name = "hickory-proto" 1779 + version = "0.25.2" 1780 + source = "registry+https://github.com/rust-lang/crates.io-index" 1781 + checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" 1782 + dependencies = [ 1783 + "async-trait", 1784 + "cfg-if", 1785 + "data-encoding", 1786 + "enum-as-inner", 1787 + "futures-channel", 1788 + "futures-io", 1789 + "futures-util", 1790 + "idna", 1791 + "ipnet", 1792 + "once_cell", 1793 + "rand", 1794 + "ring", 1795 + "thiserror", 1796 + "tinyvec", 1797 + "tokio", 1798 + "tracing", 1799 + "url", 1800 + ] 1801 + 1802 + [[package]] 1803 + name = "hickory-resolver" 1804 + version = "0.25.2" 1805 + source = "registry+https://github.com/rust-lang/crates.io-index" 1806 + checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" 1807 + dependencies = [ 1808 + "cfg-if", 1809 + "futures-util", 1810 + "hickory-proto", 1811 + "ipconfig", 1812 + "moka", 1813 + "once_cell", 1814 + "parking_lot", 1815 + "rand", 1816 + "resolv-conf", 1817 + "smallvec", 1818 + "thiserror", 1819 + "tokio", 1820 + "tracing", 1821 + ] 1822 + 1823 + [[package]] 1824 + name = "home" 1825 + version = "0.5.11" 1826 + source = "registry+https://github.com/rust-lang/crates.io-index" 1827 + checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 1828 + dependencies = [ 1829 + "windows-sys 0.59.0", 1830 + ] 1831 + 1832 + [[package]] 1833 + name = "http" 1834 + version = "1.3.1" 1835 + source = "registry+https://github.com/rust-lang/crates.io-index" 1836 + checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 1837 + dependencies = [ 1838 + "bytes", 1839 + "fnv", 1840 + "itoa", 1841 + ] 1842 + 1843 + [[package]] 1844 + name = "http-body" 1845 + version = "1.0.1" 1846 + source = "registry+https://github.com/rust-lang/crates.io-index" 1847 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1848 + dependencies = [ 1849 + "bytes", 1850 + "http", 1851 + ] 1852 + 1853 + [[package]] 1854 + name = "http-body-util" 1855 + version = "0.1.3" 1856 + source = "registry+https://github.com/rust-lang/crates.io-index" 1857 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1858 + dependencies = [ 1859 + "bytes", 1860 + "futures-core", 1861 + "http", 1862 + "http-body", 1863 + "pin-project-lite", 1864 + ] 1865 + 1866 + [[package]] 1867 + name = "httparse" 1868 + version = "1.10.1" 1869 + source = "registry+https://github.com/rust-lang/crates.io-index" 1870 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1871 + 1872 + [[package]] 1873 + name = "httpdate" 1874 + version = "1.0.3" 1875 + source = "registry+https://github.com/rust-lang/crates.io-index" 1876 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1877 + 1878 + [[package]] 1879 + name = "human_format" 1880 + version = "1.1.0" 1881 + source = "registry+https://github.com/rust-lang/crates.io-index" 1882 + checksum = "5c3b1f728c459d27b12448862017b96ad4767b1ec2ec5e6434e99f1577f085b8" 1883 + 1884 + [[package]] 1885 + name = "hyper" 1886 + version = "1.7.0" 1887 + source = "registry+https://github.com/rust-lang/crates.io-index" 1888 + checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" 1889 + dependencies = [ 1890 + "atomic-waker", 1891 + "bytes", 1892 + "futures-channel", 1893 + "futures-core", 1894 + "h2", 1895 + "http", 1896 + "http-body", 1897 + "httparse", 1898 + "httpdate", 1899 + "itoa", 1900 + "pin-project-lite", 1901 + "pin-utils", 1902 + "smallvec", 1903 + "tokio", 1904 + "want", 1905 + ] 1906 + 1907 + [[package]] 1908 + name = "hyper-rustls" 1909 + version = "0.27.7" 1910 + source = "registry+https://github.com/rust-lang/crates.io-index" 1911 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1912 + dependencies = [ 1913 + "http", 1914 + "hyper", 1915 + "hyper-util", 1916 + "rustls", 1917 + "rustls-pki-types", 1918 + "tokio", 1919 + "tokio-rustls", 1920 + "tower-service", 1921 + ] 1922 + 1923 + [[package]] 1924 + name = "hyper-tls" 1925 + version = "0.6.0" 1926 + source = "registry+https://github.com/rust-lang/crates.io-index" 1927 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1928 + dependencies = [ 1929 + "bytes", 1930 + "http-body-util", 1931 + "hyper", 1932 + "hyper-util", 1933 + "native-tls", 1934 + "tokio", 1935 + "tokio-native-tls", 1936 + "tower-service", 1937 + ] 1938 + 1939 + [[package]] 1940 + name = "hyper-util" 1941 + version = "0.1.17" 1942 + source = "registry+https://github.com/rust-lang/crates.io-index" 1943 + checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" 1944 + dependencies = [ 1945 + "base64", 1946 + "bytes", 1947 + "futures-channel", 1948 + "futures-core", 1949 + "futures-util", 1950 + "http", 1951 + "http-body", 1952 + "hyper", 1953 + "ipnet", 1954 + "libc", 1955 + "percent-encoding", 1956 + "pin-project-lite", 1957 + "socket2 0.6.0", 1958 + "system-configuration", 1959 + "tokio", 1960 + "tower-service", 1961 + "tracing", 1962 + "windows-registry", 1963 + ] 1964 + 1965 + [[package]] 1966 + name = "icu_collections" 1967 + version = "2.0.0" 1968 + source = "registry+https://github.com/rust-lang/crates.io-index" 1969 + checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 1970 + dependencies = [ 1971 + "displaydoc", 1972 + "potential_utf", 1973 + "yoke", 1974 + "zerofrom", 1975 + "zerovec", 1976 + ] 1977 + 1978 + [[package]] 1979 + name = "icu_locale_core" 1980 + version = "2.0.0" 1981 + source = "registry+https://github.com/rust-lang/crates.io-index" 1982 + checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 1983 + dependencies = [ 1984 + "displaydoc", 1985 + "litemap", 1986 + "tinystr", 1987 + "writeable", 1988 + "zerovec", 1989 + ] 1990 + 1991 + [[package]] 1992 + name = "icu_normalizer" 1993 + version = "2.0.0" 1994 + source = "registry+https://github.com/rust-lang/crates.io-index" 1995 + checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 1996 + dependencies = [ 1997 + "displaydoc", 1998 + "icu_collections", 1999 + "icu_normalizer_data", 2000 + "icu_properties", 2001 + "icu_provider", 2002 + "smallvec", 2003 + "zerovec", 2004 + ] 2005 + 2006 + [[package]] 2007 + name = "icu_normalizer_data" 2008 + version = "2.0.0" 2009 + source = "registry+https://github.com/rust-lang/crates.io-index" 2010 + checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 2011 + 2012 + [[package]] 2013 + name = "icu_properties" 2014 + version = "2.0.1" 2015 + source = "registry+https://github.com/rust-lang/crates.io-index" 2016 + checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 2017 + dependencies = [ 2018 + "displaydoc", 2019 + "icu_collections", 2020 + "icu_locale_core", 2021 + "icu_properties_data", 2022 + "icu_provider", 2023 + "potential_utf", 2024 + "zerotrie", 2025 + "zerovec", 2026 + ] 2027 + 2028 + [[package]] 2029 + name = "icu_properties_data" 2030 + version = "2.0.1" 2031 + source = "registry+https://github.com/rust-lang/crates.io-index" 2032 + checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 2033 + 2034 + [[package]] 2035 + name = "icu_provider" 2036 + version = "2.0.0" 2037 + source = "registry+https://github.com/rust-lang/crates.io-index" 2038 + checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 2039 + dependencies = [ 2040 + "displaydoc", 2041 + "icu_locale_core", 2042 + "stable_deref_trait", 2043 + "tinystr", 2044 + "writeable", 2045 + "yoke", 2046 + "zerofrom", 2047 + "zerotrie", 2048 + "zerovec", 2049 + ] 2050 + 2051 + [[package]] 2052 + name = "identity" 2053 + version = "0.0.0" 2054 + dependencies = [ 2055 + "async-trait", 2056 + "hickory-resolver", 2057 + "moka", 2058 + "reqwest", 2059 + "serde", 2060 + "serde_json", 2061 + "thiserror", 2062 + "tokio", 2063 + "tracing", 2064 + "tracing-subscriber", 2065 + "url", 2066 + ] 2067 + 2068 + [[package]] 2069 + name = "idna" 2070 + version = "1.1.0" 2071 + source = "registry+https://github.com/rust-lang/crates.io-index" 2072 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 2073 + dependencies = [ 2074 + "idna_adapter", 2075 + "smallvec", 2076 + "utf8_iter", 2077 + ] 2078 + 2079 + [[package]] 2080 + name = "idna_adapter" 2081 + version = "1.2.1" 2082 + source = "registry+https://github.com/rust-lang/crates.io-index" 2083 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 2084 + dependencies = [ 2085 + "icu_normalizer", 2086 + "icu_properties", 2087 + ] 2088 + 2089 + [[package]] 2090 + name = "imara-diff" 2091 + version = "0.1.8" 2092 + source = "registry+https://github.com/rust-lang/crates.io-index" 2093 + checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" 2094 + dependencies = [ 2095 + "hashbrown 0.15.5", 2096 + ] 2097 + 2098 + [[package]] 2099 + name = "indexmap" 2100 + version = "2.11.4" 2101 + source = "registry+https://github.com/rust-lang/crates.io-index" 2102 + checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 2103 + dependencies = [ 2104 + "equivalent", 2105 + "hashbrown 0.16.0", 2106 + ] 2107 + 2108 + [[package]] 2109 + name = "io-close" 2110 + version = "0.3.7" 2111 + source = "registry+https://github.com/rust-lang/crates.io-index" 2112 + checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" 2113 + dependencies = [ 2114 + "libc", 2115 + "winapi", 2116 + ] 2117 + 2118 + [[package]] 2119 + name = "io-uring" 2120 + version = "0.7.10" 2121 + source = "registry+https://github.com/rust-lang/crates.io-index" 2122 + checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" 2123 + dependencies = [ 2124 + "bitflags", 2125 + "cfg-if", 2126 + "libc", 2127 + ] 2128 + 2129 + [[package]] 2130 + name = "ipconfig" 2131 + version = "0.3.2" 2132 + source = "registry+https://github.com/rust-lang/crates.io-index" 2133 + checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 2134 + dependencies = [ 2135 + "socket2 0.5.10", 2136 + "widestring", 2137 + "windows-sys 0.48.0", 2138 + "winreg", 2139 + ] 2140 + 2141 + [[package]] 2142 + name = "ipnet" 2143 + version = "2.11.0" 2144 + source = "registry+https://github.com/rust-lang/crates.io-index" 2145 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 2146 + 2147 + [[package]] 2148 + name = "iri-string" 2149 + version = "0.7.8" 2150 + source = "registry+https://github.com/rust-lang/crates.io-index" 2151 + checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 2152 + dependencies = [ 2153 + "memchr", 2154 + "serde", 2155 + ] 2156 + 2157 + [[package]] 2158 + name = "is_terminal_polyfill" 2159 + version = "1.70.1" 2160 + source = "registry+https://github.com/rust-lang/crates.io-index" 2161 + checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 2162 + 2163 + [[package]] 2164 + name = "itertools" 2165 + version = "0.13.0" 2166 + source = "registry+https://github.com/rust-lang/crates.io-index" 2167 + checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 2168 + dependencies = [ 2169 + "either", 2170 + ] 2171 + 2172 + [[package]] 2173 + name = "itoa" 2174 + version = "1.0.15" 2175 + source = "registry+https://github.com/rust-lang/crates.io-index" 2176 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 2177 + 2178 + [[package]] 2179 + name = "jiff" 2180 + version = "0.2.15" 2181 + source = "registry+https://github.com/rust-lang/crates.io-index" 2182 + checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" 2183 + dependencies = [ 2184 + "jiff-static", 2185 + "jiff-tzdb-platform", 2186 + "log", 2187 + "portable-atomic", 2188 + "portable-atomic-util", 2189 + "serde", 2190 + "windows-sys 0.59.0", 2191 + ] 2192 + 2193 + [[package]] 2194 + name = "jiff-static" 2195 + version = "0.2.15" 2196 + source = "registry+https://github.com/rust-lang/crates.io-index" 2197 + checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" 2198 + dependencies = [ 2199 + "proc-macro2", 2200 + "quote", 2201 + "syn", 2202 + ] 2203 + 2204 + [[package]] 2205 + name = "jiff-tzdb" 2206 + version = "0.1.4" 2207 + source = "registry+https://github.com/rust-lang/crates.io-index" 2208 + checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" 2209 + 2210 + [[package]] 2211 + name = "jiff-tzdb-platform" 2212 + version = "0.1.3" 2213 + source = "registry+https://github.com/rust-lang/crates.io-index" 2214 + checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" 2215 + dependencies = [ 2216 + "jiff-tzdb", 2217 + ] 2218 + 2219 + [[package]] 2220 + name = "jobserver" 2221 + version = "0.1.34" 2222 + source = "registry+https://github.com/rust-lang/crates.io-index" 2223 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 2224 + dependencies = [ 2225 + "getrandom 0.3.3", 2226 + "libc", 2227 + ] 2228 + 2229 + [[package]] 2230 + name = "js-sys" 2231 + version = "0.3.81" 2232 + source = "registry+https://github.com/rust-lang/crates.io-index" 2233 + checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" 2234 + dependencies = [ 2235 + "once_cell", 2236 + "wasm-bindgen", 2237 + ] 2238 + 2239 + [[package]] 2240 + name = "knot" 2241 + version = "0.0.0" 2242 + dependencies = [ 2243 + "anyhow", 2244 + "axum", 2245 + "axum-extra", 2246 + "bytes", 2247 + "clap", 2248 + "data-encoding", 2249 + "gix", 2250 + "hyper-util", 2251 + "identity", 2252 + "reqwest", 2253 + "rustc-hash", 2254 + "serde", 2255 + "serde_json", 2256 + "thiserror", 2257 + "time", 2258 + "tokio", 2259 + "tower", 2260 + "tower-http", 2261 + "tracing", 2262 + "tracing-journald", 2263 + "tracing-subscriber", 2264 + "url", 2265 + "xrpc", 2266 + ] 2267 + 2268 + [[package]] 2269 + name = "kstring" 2270 + version = "2.0.2" 2271 + source = "registry+https://github.com/rust-lang/crates.io-index" 2272 + checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" 2273 + dependencies = [ 2274 + "static_assertions", 2275 + ] 2276 + 2277 + [[package]] 2278 + name = "lazy_static" 2279 + version = "1.5.0" 2280 + source = "registry+https://github.com/rust-lang/crates.io-index" 2281 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 2282 + 2283 + [[package]] 2284 + name = "libc" 2285 + version = "0.2.176" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" 2288 + 2289 + [[package]] 2290 + name = "libloading" 2291 + version = "0.8.8" 2292 + source = "registry+https://github.com/rust-lang/crates.io-index" 2293 + checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 2294 + dependencies = [ 2295 + "cfg-if", 2296 + "windows-targets 0.53.3", 2297 + ] 2298 + 2299 + [[package]] 2300 + name = "libredox" 2301 + version = "0.1.10" 2302 + source = "registry+https://github.com/rust-lang/crates.io-index" 2303 + checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 2304 + dependencies = [ 2305 + "bitflags", 2306 + "libc", 2307 + "redox_syscall", 2308 + ] 2309 + 2310 + [[package]] 2311 + name = "libz-rs-sys" 2312 + version = "0.5.2" 2313 + source = "registry+https://github.com/rust-lang/crates.io-index" 2314 + checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" 2315 + dependencies = [ 2316 + "zlib-rs", 2317 + ] 2318 + 2319 + [[package]] 2320 + name = "linux-raw-sys" 2321 + version = "0.11.0" 2322 + source = "registry+https://github.com/rust-lang/crates.io-index" 2323 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 2324 + 2325 + [[package]] 2326 + name = "litemap" 2327 + version = "0.8.0" 2328 + source = "registry+https://github.com/rust-lang/crates.io-index" 2329 + checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 2330 + 2331 + [[package]] 2332 + name = "lock_api" 2333 + version = "0.4.13" 2334 + source = "registry+https://github.com/rust-lang/crates.io-index" 2335 + checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 2336 + dependencies = [ 2337 + "autocfg", 2338 + "scopeguard", 2339 + ] 2340 + 2341 + [[package]] 2342 + name = "log" 2343 + version = "0.4.28" 2344 + source = "registry+https://github.com/rust-lang/crates.io-index" 2345 + checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 2346 + 2347 + [[package]] 2348 + name = "matchit" 2349 + version = "0.8.4" 2350 + source = "registry+https://github.com/rust-lang/crates.io-index" 2351 + checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 2352 + 2353 + [[package]] 2354 + name = "maybe-async" 2355 + version = "0.2.10" 2356 + source = "registry+https://github.com/rust-lang/crates.io-index" 2357 + checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" 2358 + dependencies = [ 2359 + "proc-macro2", 2360 + "quote", 2361 + "syn", 2362 + ] 2363 + 2364 + [[package]] 2365 + name = "memchr" 2366 + version = "2.7.6" 2367 + source = "registry+https://github.com/rust-lang/crates.io-index" 2368 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 2369 + 2370 + [[package]] 2371 + name = "memmap2" 2372 + version = "0.9.8" 2373 + source = "registry+https://github.com/rust-lang/crates.io-index" 2374 + checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" 2375 + dependencies = [ 2376 + "libc", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "mime" 2381 + version = "0.3.17" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 2384 + 2385 + [[package]] 2386 + name = "minimal-lexical" 2387 + version = "0.2.1" 2388 + source = "registry+https://github.com/rust-lang/crates.io-index" 2389 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2390 + 2391 + [[package]] 2392 + name = "miniz_oxide" 2393 + version = "0.8.9" 2394 + source = "registry+https://github.com/rust-lang/crates.io-index" 2395 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 2396 + dependencies = [ 2397 + "adler2", 2398 + ] 2399 + 2400 + [[package]] 2401 + name = "mio" 2402 + version = "1.0.4" 2403 + source = "registry+https://github.com/rust-lang/crates.io-index" 2404 + checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 2405 + dependencies = [ 2406 + "libc", 2407 + "wasi 0.11.1+wasi-snapshot-preview1", 2408 + "windows-sys 0.59.0", 2409 + ] 2410 + 2411 + [[package]] 2412 + name = "moka" 2413 + version = "0.12.11" 2414 + source = "registry+https://github.com/rust-lang/crates.io-index" 2415 + checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" 2416 + dependencies = [ 2417 + "crossbeam-channel", 2418 + "crossbeam-epoch", 2419 + "crossbeam-utils", 2420 + "equivalent", 2421 + "parking_lot", 2422 + "portable-atomic", 2423 + "rustc_version", 2424 + "smallvec", 2425 + "tagptr", 2426 + "uuid", 2427 + ] 2428 + 2429 + [[package]] 2430 + name = "multibase" 2431 + version = "0.9.1" 2432 + source = "registry+https://github.com/rust-lang/crates.io-index" 2433 + checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" 2434 + dependencies = [ 2435 + "base-x", 2436 + "data-encoding", 2437 + "data-encoding-macro", 2438 + ] 2439 + 2440 + [[package]] 2441 + name = "native-tls" 2442 + version = "0.2.14" 2443 + source = "registry+https://github.com/rust-lang/crates.io-index" 2444 + checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 2445 + dependencies = [ 2446 + "libc", 2447 + "log", 2448 + "openssl", 2449 + "openssl-probe", 2450 + "openssl-sys", 2451 + "schannel", 2452 + "security-framework", 2453 + "security-framework-sys", 2454 + "tempfile", 2455 + ] 2456 + 2457 + [[package]] 2458 + name = "nom" 2459 + version = "7.1.3" 2460 + source = "registry+https://github.com/rust-lang/crates.io-index" 2461 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 2462 + dependencies = [ 2463 + "memchr", 2464 + "minimal-lexical", 2465 + ] 2466 + 2467 + [[package]] 2468 + name = "nu-ansi-term" 2469 + version = "0.50.1" 2470 + source = "registry+https://github.com/rust-lang/crates.io-index" 2471 + checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" 2472 + dependencies = [ 2473 + "windows-sys 0.52.0", 2474 + ] 2475 + 2476 + [[package]] 2477 + name = "num-conv" 2478 + version = "0.1.0" 2479 + source = "registry+https://github.com/rust-lang/crates.io-index" 2480 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 2481 + 2482 + [[package]] 2483 + name = "object" 2484 + version = "0.36.7" 2485 + source = "registry+https://github.com/rust-lang/crates.io-index" 2486 + checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 2487 + dependencies = [ 2488 + "memchr", 2489 + ] 2490 + 2491 + [[package]] 2492 + name = "once_cell" 2493 + version = "1.21.3" 2494 + source = "registry+https://github.com/rust-lang/crates.io-index" 2495 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 2496 + dependencies = [ 2497 + "critical-section", 2498 + "portable-atomic", 2499 + ] 2500 + 2501 + [[package]] 2502 + name = "once_cell_polyfill" 2503 + version = "1.70.1" 2504 + source = "registry+https://github.com/rust-lang/crates.io-index" 2505 + checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 2506 + 2507 + [[package]] 2508 + name = "openssl" 2509 + version = "0.10.73" 2510 + source = "registry+https://github.com/rust-lang/crates.io-index" 2511 + checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 2512 + dependencies = [ 2513 + "bitflags", 2514 + "cfg-if", 2515 + "foreign-types", 2516 + "libc", 2517 + "once_cell", 2518 + "openssl-macros", 2519 + "openssl-sys", 2520 + ] 2521 + 2522 + [[package]] 2523 + name = "openssl-macros" 2524 + version = "0.1.1" 2525 + source = "registry+https://github.com/rust-lang/crates.io-index" 2526 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 2527 + dependencies = [ 2528 + "proc-macro2", 2529 + "quote", 2530 + "syn", 2531 + ] 2532 + 2533 + [[package]] 2534 + name = "openssl-probe" 2535 + version = "0.1.6" 2536 + source = "registry+https://github.com/rust-lang/crates.io-index" 2537 + checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 2538 + 2539 + [[package]] 2540 + name = "openssl-sys" 2541 + version = "0.9.109" 2542 + source = "registry+https://github.com/rust-lang/crates.io-index" 2543 + checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 2544 + dependencies = [ 2545 + "cc", 2546 + "libc", 2547 + "pkg-config", 2548 + "vcpkg", 2549 + ] 2550 + 2551 + [[package]] 2552 + name = "parking_lot" 2553 + version = "0.12.4" 2554 + source = "registry+https://github.com/rust-lang/crates.io-index" 2555 + checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 2556 + dependencies = [ 2557 + "lock_api", 2558 + "parking_lot_core", 2559 + ] 2560 + 2561 + [[package]] 2562 + name = "parking_lot_core" 2563 + version = "0.9.11" 2564 + source = "registry+https://github.com/rust-lang/crates.io-index" 2565 + checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 2566 + dependencies = [ 2567 + "cfg-if", 2568 + "libc", 2569 + "redox_syscall", 2570 + "smallvec", 2571 + "windows-targets 0.52.6", 2572 + ] 2573 + 2574 + [[package]] 2575 + name = "percent-encoding" 2576 + version = "2.3.2" 2577 + source = "registry+https://github.com/rust-lang/crates.io-index" 2578 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2579 + 2580 + [[package]] 2581 + name = "pin-project-lite" 2582 + version = "0.2.16" 2583 + source = "registry+https://github.com/rust-lang/crates.io-index" 2584 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 2585 + 2586 + [[package]] 2587 + name = "pin-utils" 2588 + version = "0.1.0" 2589 + source = "registry+https://github.com/rust-lang/crates.io-index" 2590 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2591 + 2592 + [[package]] 2593 + name = "pkg-config" 2594 + version = "0.3.32" 2595 + source = "registry+https://github.com/rust-lang/crates.io-index" 2596 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2597 + 2598 + [[package]] 2599 + name = "portable-atomic" 2600 + version = "1.11.1" 2601 + source = "registry+https://github.com/rust-lang/crates.io-index" 2602 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 2603 + 2604 + [[package]] 2605 + name = "portable-atomic-util" 2606 + version = "0.2.4" 2607 + source = "registry+https://github.com/rust-lang/crates.io-index" 2608 + checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 2609 + dependencies = [ 2610 + "portable-atomic", 2611 + ] 2612 + 2613 + [[package]] 2614 + name = "potential_utf" 2615 + version = "0.1.3" 2616 + source = "registry+https://github.com/rust-lang/crates.io-index" 2617 + checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" 2618 + dependencies = [ 2619 + "zerovec", 2620 + ] 2621 + 2622 + [[package]] 2623 + name = "powerfmt" 2624 + version = "0.2.0" 2625 + source = "registry+https://github.com/rust-lang/crates.io-index" 2626 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2627 + 2628 + [[package]] 2629 + name = "ppv-lite86" 2630 + version = "0.2.21" 2631 + source = "registry+https://github.com/rust-lang/crates.io-index" 2632 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2633 + dependencies = [ 2634 + "zerocopy", 2635 + ] 2636 + 2637 + [[package]] 2638 + name = "prettyplease" 2639 + version = "0.2.37" 2640 + source = "registry+https://github.com/rust-lang/crates.io-index" 2641 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2642 + dependencies = [ 2643 + "proc-macro2", 2644 + "syn", 2645 + ] 2646 + 2647 + [[package]] 2648 + name = "proc-macro2" 2649 + version = "1.0.101" 2650 + source = "registry+https://github.com/rust-lang/crates.io-index" 2651 + checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 2652 + dependencies = [ 2653 + "unicode-ident", 2654 + ] 2655 + 2656 + [[package]] 2657 + name = "prodash" 2658 + version = "30.0.1" 2659 + source = "registry+https://github.com/rust-lang/crates.io-index" 2660 + checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" 2661 + dependencies = [ 2662 + "bytesize", 2663 + "human_format", 2664 + "parking_lot", 2665 + ] 2666 + 2667 + [[package]] 2668 + name = "quote" 2669 + version = "1.0.40" 2670 + source = "registry+https://github.com/rust-lang/crates.io-index" 2671 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 2672 + dependencies = [ 2673 + "proc-macro2", 2674 + ] 2675 + 2676 + [[package]] 2677 + name = "r-efi" 2678 + version = "5.3.0" 2679 + source = "registry+https://github.com/rust-lang/crates.io-index" 2680 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2681 + 2682 + [[package]] 2683 + name = "rand" 2684 + version = "0.9.2" 2685 + source = "registry+https://github.com/rust-lang/crates.io-index" 2686 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 2687 + dependencies = [ 2688 + "rand_chacha", 2689 + "rand_core", 2690 + ] 2691 + 2692 + [[package]] 2693 + name = "rand_chacha" 2694 + version = "0.9.0" 2695 + source = "registry+https://github.com/rust-lang/crates.io-index" 2696 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 2697 + dependencies = [ 2698 + "ppv-lite86", 2699 + "rand_core", 2700 + ] 2701 + 2702 + [[package]] 2703 + name = "rand_core" 2704 + version = "0.9.3" 2705 + source = "registry+https://github.com/rust-lang/crates.io-index" 2706 + checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 2707 + dependencies = [ 2708 + "getrandom 0.3.3", 2709 + ] 2710 + 2711 + [[package]] 2712 + name = "redox_syscall" 2713 + version = "0.5.17" 2714 + source = "registry+https://github.com/rust-lang/crates.io-index" 2715 + checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 2716 + dependencies = [ 2717 + "bitflags", 2718 + ] 2719 + 2720 + [[package]] 2721 + name = "regex" 2722 + version = "1.11.3" 2723 + source = "registry+https://github.com/rust-lang/crates.io-index" 2724 + checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" 2725 + dependencies = [ 2726 + "aho-corasick", 2727 + "memchr", 2728 + "regex-automata", 2729 + "regex-syntax", 2730 + ] 2731 + 2732 + [[package]] 2733 + name = "regex-automata" 2734 + version = "0.4.11" 2735 + source = "registry+https://github.com/rust-lang/crates.io-index" 2736 + checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" 2737 + dependencies = [ 2738 + "aho-corasick", 2739 + "memchr", 2740 + "regex-syntax", 2741 + ] 2742 + 2743 + [[package]] 2744 + name = "regex-syntax" 2745 + version = "0.8.6" 2746 + source = "registry+https://github.com/rust-lang/crates.io-index" 2747 + checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" 2748 + 2749 + [[package]] 2750 + name = "reqwest" 2751 + version = "0.12.23" 2752 + source = "registry+https://github.com/rust-lang/crates.io-index" 2753 + checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" 2754 + dependencies = [ 2755 + "base64", 2756 + "bytes", 2757 + "encoding_rs", 2758 + "futures-core", 2759 + "h2", 2760 + "http", 2761 + "http-body", 2762 + "http-body-util", 2763 + "hyper", 2764 + "hyper-rustls", 2765 + "hyper-tls", 2766 + "hyper-util", 2767 + "js-sys", 2768 + "log", 2769 + "mime", 2770 + "native-tls", 2771 + "percent-encoding", 2772 + "pin-project-lite", 2773 + "rustls-pki-types", 2774 + "serde", 2775 + "serde_json", 2776 + "serde_urlencoded", 2777 + "sync_wrapper", 2778 + "tokio", 2779 + "tokio-native-tls", 2780 + "tower", 2781 + "tower-http", 2782 + "tower-service", 2783 + "url", 2784 + "wasm-bindgen", 2785 + "wasm-bindgen-futures", 2786 + "web-sys", 2787 + ] 2788 + 2789 + [[package]] 2790 + name = "resolv-conf" 2791 + version = "0.7.5" 2792 + source = "registry+https://github.com/rust-lang/crates.io-index" 2793 + checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" 2794 + 2795 + [[package]] 2796 + name = "ring" 2797 + version = "0.17.14" 2798 + source = "registry+https://github.com/rust-lang/crates.io-index" 2799 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2800 + dependencies = [ 2801 + "cc", 2802 + "cfg-if", 2803 + "getrandom 0.2.16", 2804 + "libc", 2805 + "untrusted", 2806 + "windows-sys 0.52.0", 2807 + ] 2808 + 2809 + [[package]] 2810 + name = "rustc-demangle" 2811 + version = "0.1.26" 2812 + source = "registry+https://github.com/rust-lang/crates.io-index" 2813 + checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 2814 + 2815 + [[package]] 2816 + name = "rustc-hash" 2817 + version = "2.1.1" 2818 + source = "registry+https://github.com/rust-lang/crates.io-index" 2819 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 2820 + 2821 + [[package]] 2822 + name = "rustc_version" 2823 + version = "0.4.1" 2824 + source = "registry+https://github.com/rust-lang/crates.io-index" 2825 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2826 + dependencies = [ 2827 + "semver", 2828 + ] 2829 + 2830 + [[package]] 2831 + name = "rustix" 2832 + version = "1.1.2" 2833 + source = "registry+https://github.com/rust-lang/crates.io-index" 2834 + checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 2835 + dependencies = [ 2836 + "bitflags", 2837 + "errno", 2838 + "libc", 2839 + "linux-raw-sys", 2840 + "windows-sys 0.61.0", 2841 + ] 2842 + 2843 + [[package]] 2844 + name = "rustls" 2845 + version = "0.23.32" 2846 + source = "registry+https://github.com/rust-lang/crates.io-index" 2847 + checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" 2848 + dependencies = [ 2849 + "once_cell", 2850 + "rustls-pki-types", 2851 + "rustls-webpki", 2852 + "subtle", 2853 + "zeroize", 2854 + ] 2855 + 2856 + [[package]] 2857 + name = "rustls-pki-types" 2858 + version = "1.12.0" 2859 + source = "registry+https://github.com/rust-lang/crates.io-index" 2860 + checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2861 + dependencies = [ 2862 + "zeroize", 2863 + ] 2864 + 2865 + [[package]] 2866 + name = "rustls-webpki" 2867 + version = "0.103.6" 2868 + source = "registry+https://github.com/rust-lang/crates.io-index" 2869 + checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" 2870 + dependencies = [ 2871 + "ring", 2872 + "rustls-pki-types", 2873 + "untrusted", 2874 + ] 2875 + 2876 + [[package]] 2877 + name = "rustversion" 2878 + version = "1.0.22" 2879 + source = "registry+https://github.com/rust-lang/crates.io-index" 2880 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 2881 + 2882 + [[package]] 2883 + name = "ryu" 2884 + version = "1.0.20" 2885 + source = "registry+https://github.com/rust-lang/crates.io-index" 2886 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 2887 + 2888 + [[package]] 2889 + name = "same-file" 2890 + version = "1.0.6" 2891 + source = "registry+https://github.com/rust-lang/crates.io-index" 2892 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2893 + dependencies = [ 2894 + "winapi-util", 2895 + ] 2896 + 2897 + [[package]] 2898 + name = "schannel" 2899 + version = "0.1.28" 2900 + source = "registry+https://github.com/rust-lang/crates.io-index" 2901 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 2902 + dependencies = [ 2903 + "windows-sys 0.61.0", 2904 + ] 2905 + 2906 + [[package]] 2907 + name = "scopeguard" 2908 + version = "1.2.0" 2909 + source = "registry+https://github.com/rust-lang/crates.io-index" 2910 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2911 + 2912 + [[package]] 2913 + name = "security-framework" 2914 + version = "2.11.1" 2915 + source = "registry+https://github.com/rust-lang/crates.io-index" 2916 + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2917 + dependencies = [ 2918 + "bitflags", 2919 + "core-foundation", 2920 + "core-foundation-sys", 2921 + "libc", 2922 + "security-framework-sys", 2923 + ] 2924 + 2925 + [[package]] 2926 + name = "security-framework-sys" 2927 + version = "2.15.0" 2928 + source = "registry+https://github.com/rust-lang/crates.io-index" 2929 + checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 2930 + dependencies = [ 2931 + "core-foundation-sys", 2932 + "libc", 2933 + ] 2934 + 2935 + [[package]] 2936 + name = "semver" 2937 + version = "1.0.27" 2938 + source = "registry+https://github.com/rust-lang/crates.io-index" 2939 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 2940 + 2941 + [[package]] 2942 + name = "serde" 2943 + version = "1.0.226" 2944 + source = "registry+https://github.com/rust-lang/crates.io-index" 2945 + checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" 2946 + dependencies = [ 2947 + "serde_core", 2948 + "serde_derive", 2949 + ] 2950 + 2951 + [[package]] 2952 + name = "serde_core" 2953 + version = "1.0.226" 2954 + source = "registry+https://github.com/rust-lang/crates.io-index" 2955 + checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" 2956 + dependencies = [ 2957 + "serde_derive", 2958 + ] 2959 + 2960 + [[package]] 2961 + name = "serde_derive" 2962 + version = "1.0.226" 2963 + source = "registry+https://github.com/rust-lang/crates.io-index" 2964 + checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" 2965 + dependencies = [ 2966 + "proc-macro2", 2967 + "quote", 2968 + "syn", 2969 + ] 2970 + 2971 + [[package]] 2972 + name = "serde_json" 2973 + version = "1.0.145" 2974 + source = "registry+https://github.com/rust-lang/crates.io-index" 2975 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2976 + dependencies = [ 2977 + "itoa", 2978 + "memchr", 2979 + "ryu", 2980 + "serde", 2981 + "serde_core", 2982 + ] 2983 + 2984 + [[package]] 2985 + name = "serde_path_to_error" 2986 + version = "0.1.20" 2987 + source = "registry+https://github.com/rust-lang/crates.io-index" 2988 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 2989 + dependencies = [ 2990 + "itoa", 2991 + "serde", 2992 + "serde_core", 2993 + ] 2994 + 2995 + [[package]] 2996 + name = "serde_urlencoded" 2997 + version = "0.7.1" 2998 + source = "registry+https://github.com/rust-lang/crates.io-index" 2999 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 3000 + dependencies = [ 3001 + "form_urlencoded", 3002 + "itoa", 3003 + "ryu", 3004 + "serde", 3005 + ] 3006 + 3007 + [[package]] 3008 + name = "service_auth" 3009 + version = "0.0.0" 3010 + dependencies = [ 3011 + "aws-lc-rs", 3012 + "axum", 3013 + "data-encoding", 3014 + "identity", 3015 + "multibase", 3016 + "serde", 3017 + "serde_json", 3018 + "thiserror", 3019 + "time", 3020 + "tracing", 3021 + ] 3022 + 3023 + [[package]] 3024 + name = "sha1" 3025 + version = "0.10.6" 3026 + source = "registry+https://github.com/rust-lang/crates.io-index" 3027 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 3028 + dependencies = [ 3029 + "cfg-if", 3030 + "cpufeatures", 3031 + "digest", 3032 + ] 3033 + 3034 + [[package]] 3035 + name = "sha1-checked" 3036 + version = "0.10.0" 3037 + source = "registry+https://github.com/rust-lang/crates.io-index" 3038 + checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" 3039 + dependencies = [ 3040 + "digest", 3041 + "sha1", 3042 + ] 3043 + 3044 + [[package]] 3045 + name = "sharded-slab" 3046 + version = "0.1.7" 3047 + source = "registry+https://github.com/rust-lang/crates.io-index" 3048 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 3049 + dependencies = [ 3050 + "lazy_static", 3051 + ] 3052 + 3053 + [[package]] 3054 + name = "shell-words" 3055 + version = "1.1.0" 3056 + source = "registry+https://github.com/rust-lang/crates.io-index" 3057 + checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 3058 + 3059 + [[package]] 3060 + name = "shlex" 3061 + version = "1.3.0" 3062 + source = "registry+https://github.com/rust-lang/crates.io-index" 3063 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 3064 + 3065 + [[package]] 3066 + name = "signal-hook" 3067 + version = "0.3.18" 3068 + source = "registry+https://github.com/rust-lang/crates.io-index" 3069 + checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 3070 + dependencies = [ 3071 + "libc", 3072 + "signal-hook-registry", 3073 + ] 3074 + 3075 + [[package]] 3076 + name = "signal-hook-registry" 3077 + version = "1.4.6" 3078 + source = "registry+https://github.com/rust-lang/crates.io-index" 3079 + checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 3080 + dependencies = [ 3081 + "libc", 3082 + ] 3083 + 3084 + [[package]] 3085 + name = "slab" 3086 + version = "0.4.11" 3087 + source = "registry+https://github.com/rust-lang/crates.io-index" 3088 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 3089 + 3090 + [[package]] 3091 + name = "smallvec" 3092 + version = "1.15.1" 3093 + source = "registry+https://github.com/rust-lang/crates.io-index" 3094 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 3095 + 3096 + [[package]] 3097 + name = "socket2" 3098 + version = "0.5.10" 3099 + source = "registry+https://github.com/rust-lang/crates.io-index" 3100 + checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 3101 + dependencies = [ 3102 + "libc", 3103 + "windows-sys 0.52.0", 3104 + ] 3105 + 3106 + [[package]] 3107 + name = "socket2" 3108 + version = "0.6.0" 3109 + source = "registry+https://github.com/rust-lang/crates.io-index" 3110 + checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 3111 + dependencies = [ 3112 + "libc", 3113 + "windows-sys 0.59.0", 3114 + ] 3115 + 3116 + [[package]] 3117 + name = "stable_deref_trait" 3118 + version = "1.2.0" 3119 + source = "registry+https://github.com/rust-lang/crates.io-index" 3120 + checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 3121 + 3122 + [[package]] 3123 + name = "static_assertions" 3124 + version = "1.1.0" 3125 + source = "registry+https://github.com/rust-lang/crates.io-index" 3126 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 3127 + 3128 + [[package]] 3129 + name = "strsim" 3130 + version = "0.11.1" 3131 + source = "registry+https://github.com/rust-lang/crates.io-index" 3132 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3133 + 3134 + [[package]] 3135 + name = "subtle" 3136 + version = "2.6.1" 3137 + source = "registry+https://github.com/rust-lang/crates.io-index" 3138 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 3139 + 3140 + [[package]] 3141 + name = "syn" 3142 + version = "2.0.106" 3143 + source = "registry+https://github.com/rust-lang/crates.io-index" 3144 + checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 3145 + dependencies = [ 3146 + "proc-macro2", 3147 + "quote", 3148 + "unicode-ident", 3149 + ] 3150 + 3151 + [[package]] 3152 + name = "sync_wrapper" 3153 + version = "1.0.2" 3154 + source = "registry+https://github.com/rust-lang/crates.io-index" 3155 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3156 + dependencies = [ 3157 + "futures-core", 3158 + ] 3159 + 3160 + [[package]] 3161 + name = "synstructure" 3162 + version = "0.13.2" 3163 + source = "registry+https://github.com/rust-lang/crates.io-index" 3164 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 3165 + dependencies = [ 3166 + "proc-macro2", 3167 + "quote", 3168 + "syn", 3169 + ] 3170 + 3171 + [[package]] 3172 + name = "system-configuration" 3173 + version = "0.6.1" 3174 + source = "registry+https://github.com/rust-lang/crates.io-index" 3175 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 3176 + dependencies = [ 3177 + "bitflags", 3178 + "core-foundation", 3179 + "system-configuration-sys", 3180 + ] 3181 + 3182 + [[package]] 3183 + name = "system-configuration-sys" 3184 + version = "0.6.0" 3185 + source = "registry+https://github.com/rust-lang/crates.io-index" 3186 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3187 + dependencies = [ 3188 + "core-foundation-sys", 3189 + "libc", 3190 + ] 3191 + 3192 + [[package]] 3193 + name = "tagptr" 3194 + version = "0.2.0" 3195 + source = "registry+https://github.com/rust-lang/crates.io-index" 3196 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 3197 + 3198 + [[package]] 3199 + name = "tempfile" 3200 + version = "3.23.0" 3201 + source = "registry+https://github.com/rust-lang/crates.io-index" 3202 + checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 3203 + dependencies = [ 3204 + "fastrand", 3205 + "getrandom 0.3.3", 3206 + "once_cell", 3207 + "rustix", 3208 + "windows-sys 0.61.0", 3209 + ] 3210 + 3211 + [[package]] 3212 + name = "thiserror" 3213 + version = "2.0.16" 3214 + source = "registry+https://github.com/rust-lang/crates.io-index" 3215 + checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 3216 + dependencies = [ 3217 + "thiserror-impl", 3218 + ] 3219 + 3220 + [[package]] 3221 + name = "thiserror-impl" 3222 + version = "2.0.16" 3223 + source = "registry+https://github.com/rust-lang/crates.io-index" 3224 + checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 3225 + dependencies = [ 3226 + "proc-macro2", 3227 + "quote", 3228 + "syn", 3229 + ] 3230 + 3231 + [[package]] 3232 + name = "thread_local" 3233 + version = "1.1.9" 3234 + source = "registry+https://github.com/rust-lang/crates.io-index" 3235 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 3236 + dependencies = [ 3237 + "cfg-if", 3238 + ] 3239 + 3240 + [[package]] 3241 + name = "time" 3242 + version = "0.3.44" 3243 + source = "registry+https://github.com/rust-lang/crates.io-index" 3244 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3245 + dependencies = [ 3246 + "deranged", 3247 + "itoa", 3248 + "num-conv", 3249 + "powerfmt", 3250 + "serde", 3251 + "time-core", 3252 + "time-macros", 3253 + ] 3254 + 3255 + [[package]] 3256 + name = "time-core" 3257 + version = "0.1.6" 3258 + source = "registry+https://github.com/rust-lang/crates.io-index" 3259 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3260 + 3261 + [[package]] 3262 + name = "time-macros" 3263 + version = "0.2.24" 3264 + source = "registry+https://github.com/rust-lang/crates.io-index" 3265 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 3266 + dependencies = [ 3267 + "num-conv", 3268 + "time-core", 3269 + ] 3270 + 3271 + [[package]] 3272 + name = "tinystr" 3273 + version = "0.8.1" 3274 + source = "registry+https://github.com/rust-lang/crates.io-index" 3275 + checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 3276 + dependencies = [ 3277 + "displaydoc", 3278 + "zerovec", 3279 + ] 3280 + 3281 + [[package]] 3282 + name = "tinyvec" 3283 + version = "1.10.0" 3284 + source = "registry+https://github.com/rust-lang/crates.io-index" 3285 + checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 3286 + dependencies = [ 3287 + "tinyvec_macros", 3288 + ] 3289 + 3290 + [[package]] 3291 + name = "tinyvec_macros" 3292 + version = "0.1.1" 3293 + source = "registry+https://github.com/rust-lang/crates.io-index" 3294 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 3295 + 3296 + [[package]] 3297 + name = "tokio" 3298 + version = "1.47.1" 3299 + source = "registry+https://github.com/rust-lang/crates.io-index" 3300 + checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" 3301 + dependencies = [ 3302 + "backtrace", 3303 + "bytes", 3304 + "io-uring", 3305 + "libc", 3306 + "mio", 3307 + "pin-project-lite", 3308 + "signal-hook-registry", 3309 + "slab", 3310 + "socket2 0.6.0", 3311 + "tokio-macros", 3312 + "windows-sys 0.59.0", 3313 + ] 3314 + 3315 + [[package]] 3316 + name = "tokio-macros" 3317 + version = "2.5.0" 3318 + source = "registry+https://github.com/rust-lang/crates.io-index" 3319 + checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 3320 + dependencies = [ 3321 + "proc-macro2", 3322 + "quote", 3323 + "syn", 3324 + ] 3325 + 3326 + [[package]] 3327 + name = "tokio-native-tls" 3328 + version = "0.3.1" 3329 + source = "registry+https://github.com/rust-lang/crates.io-index" 3330 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 3331 + dependencies = [ 3332 + "native-tls", 3333 + "tokio", 3334 + ] 3335 + 3336 + [[package]] 3337 + name = "tokio-rustls" 3338 + version = "0.26.3" 3339 + source = "registry+https://github.com/rust-lang/crates.io-index" 3340 + checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" 3341 + dependencies = [ 3342 + "rustls", 3343 + "tokio", 3344 + ] 3345 + 3346 + [[package]] 3347 + name = "tokio-tungstenite" 3348 + version = "0.26.2" 3349 + source = "registry+https://github.com/rust-lang/crates.io-index" 3350 + checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" 3351 + dependencies = [ 3352 + "futures-util", 3353 + "log", 3354 + "tokio", 3355 + "tungstenite", 3356 + ] 3357 + 3358 + [[package]] 3359 + name = "tokio-util" 3360 + version = "0.7.16" 3361 + source = "registry+https://github.com/rust-lang/crates.io-index" 3362 + checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 3363 + dependencies = [ 3364 + "bytes", 3365 + "futures-core", 3366 + "futures-sink", 3367 + "pin-project-lite", 3368 + "tokio", 3369 + ] 3370 + 3371 + [[package]] 3372 + name = "tower" 3373 + version = "0.5.2" 3374 + source = "registry+https://github.com/rust-lang/crates.io-index" 3375 + checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 3376 + dependencies = [ 3377 + "futures-core", 3378 + "futures-util", 3379 + "pin-project-lite", 3380 + "sync_wrapper", 3381 + "tokio", 3382 + "tower-layer", 3383 + "tower-service", 3384 + "tracing", 3385 + ] 3386 + 3387 + [[package]] 3388 + name = "tower-http" 3389 + version = "0.6.6" 3390 + source = "registry+https://github.com/rust-lang/crates.io-index" 3391 + checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 3392 + dependencies = [ 3393 + "async-compression", 3394 + "bitflags", 3395 + "bytes", 3396 + "futures-core", 3397 + "futures-util", 3398 + "http", 3399 + "http-body", 3400 + "http-body-util", 3401 + "iri-string", 3402 + "pin-project-lite", 3403 + "tokio", 3404 + "tokio-util", 3405 + "tower", 3406 + "tower-layer", 3407 + "tower-service", 3408 + "tracing", 3409 + "uuid", 3410 + ] 3411 + 3412 + [[package]] 3413 + name = "tower-layer" 3414 + version = "0.3.3" 3415 + source = "registry+https://github.com/rust-lang/crates.io-index" 3416 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 3417 + 3418 + [[package]] 3419 + name = "tower-service" 3420 + version = "0.3.3" 3421 + source = "registry+https://github.com/rust-lang/crates.io-index" 3422 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 3423 + 3424 + [[package]] 3425 + name = "tracing" 3426 + version = "0.1.41" 3427 + source = "registry+https://github.com/rust-lang/crates.io-index" 3428 + checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 3429 + dependencies = [ 3430 + "log", 3431 + "pin-project-lite", 3432 + "tracing-attributes", 3433 + "tracing-core", 3434 + ] 3435 + 3436 + [[package]] 3437 + name = "tracing-attributes" 3438 + version = "0.1.30" 3439 + source = "registry+https://github.com/rust-lang/crates.io-index" 3440 + checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 3441 + dependencies = [ 3442 + "proc-macro2", 3443 + "quote", 3444 + "syn", 3445 + ] 3446 + 3447 + [[package]] 3448 + name = "tracing-core" 3449 + version = "0.1.34" 3450 + source = "registry+https://github.com/rust-lang/crates.io-index" 3451 + checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 3452 + dependencies = [ 3453 + "once_cell", 3454 + "valuable", 3455 + ] 3456 + 3457 + [[package]] 3458 + name = "tracing-journald" 3459 + version = "0.3.1" 3460 + source = "registry+https://github.com/rust-lang/crates.io-index" 3461 + checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657" 3462 + dependencies = [ 3463 + "libc", 3464 + "tracing-core", 3465 + "tracing-subscriber", 3466 + ] 3467 + 3468 + [[package]] 3469 + name = "tracing-log" 3470 + version = "0.2.0" 3471 + source = "registry+https://github.com/rust-lang/crates.io-index" 3472 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3473 + dependencies = [ 3474 + "log", 3475 + "once_cell", 3476 + "tracing-core", 3477 + ] 3478 + 3479 + [[package]] 3480 + name = "tracing-subscriber" 3481 + version = "0.3.20" 3482 + source = "registry+https://github.com/rust-lang/crates.io-index" 3483 + checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 3484 + dependencies = [ 3485 + "nu-ansi-term", 3486 + "sharded-slab", 3487 + "smallvec", 3488 + "thread_local", 3489 + "tracing-core", 3490 + "tracing-log", 3491 + ] 3492 + 3493 + [[package]] 3494 + name = "try-lock" 3495 + version = "0.2.5" 3496 + source = "registry+https://github.com/rust-lang/crates.io-index" 3497 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3498 + 3499 + [[package]] 3500 + name = "tungstenite" 3501 + version = "0.26.2" 3502 + source = "registry+https://github.com/rust-lang/crates.io-index" 3503 + checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" 3504 + dependencies = [ 3505 + "bytes", 3506 + "data-encoding", 3507 + "http", 3508 + "httparse", 3509 + "log", 3510 + "rand", 3511 + "sha1", 3512 + "thiserror", 3513 + "utf-8", 3514 + ] 3515 + 3516 + [[package]] 3517 + name = "typenum" 3518 + version = "1.18.0" 3519 + source = "registry+https://github.com/rust-lang/crates.io-index" 3520 + checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 3521 + 3522 + [[package]] 3523 + name = "uluru" 3524 + version = "3.1.0" 3525 + source = "registry+https://github.com/rust-lang/crates.io-index" 3526 + checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" 3527 + dependencies = [ 3528 + "arrayvec", 3529 + ] 3530 + 3531 + [[package]] 3532 + name = "unicode-bom" 3533 + version = "2.0.3" 3534 + source = "registry+https://github.com/rust-lang/crates.io-index" 3535 + checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" 3536 + 3537 + [[package]] 3538 + name = "unicode-ident" 3539 + version = "1.0.19" 3540 + source = "registry+https://github.com/rust-lang/crates.io-index" 3541 + checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 3542 + 3543 + [[package]] 3544 + name = "unicode-normalization" 3545 + version = "0.1.24" 3546 + source = "registry+https://github.com/rust-lang/crates.io-index" 3547 + checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 3548 + dependencies = [ 3549 + "tinyvec", 3550 + ] 3551 + 3552 + [[package]] 3553 + name = "untrusted" 3554 + version = "0.9.0" 3555 + source = "registry+https://github.com/rust-lang/crates.io-index" 3556 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3557 + 3558 + [[package]] 3559 + name = "url" 3560 + version = "2.5.7" 3561 + source = "registry+https://github.com/rust-lang/crates.io-index" 3562 + checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 3563 + dependencies = [ 3564 + "form_urlencoded", 3565 + "idna", 3566 + "percent-encoding", 3567 + "serde", 3568 + ] 3569 + 3570 + [[package]] 3571 + name = "utf-8" 3572 + version = "0.7.6" 3573 + source = "registry+https://github.com/rust-lang/crates.io-index" 3574 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 3575 + 3576 + [[package]] 3577 + name = "utf8_iter" 3578 + version = "1.0.4" 3579 + source = "registry+https://github.com/rust-lang/crates.io-index" 3580 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3581 + 3582 + [[package]] 3583 + name = "utf8parse" 3584 + version = "0.2.2" 3585 + source = "registry+https://github.com/rust-lang/crates.io-index" 3586 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3587 + 3588 + [[package]] 3589 + name = "uuid" 3590 + version = "1.18.1" 3591 + source = "registry+https://github.com/rust-lang/crates.io-index" 3592 + checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" 3593 + dependencies = [ 3594 + "getrandom 0.3.3", 3595 + "js-sys", 3596 + "wasm-bindgen", 3597 + ] 3598 + 3599 + [[package]] 3600 + name = "valuable" 3601 + version = "0.1.1" 3602 + source = "registry+https://github.com/rust-lang/crates.io-index" 3603 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 3604 + 3605 + [[package]] 3606 + name = "vcpkg" 3607 + version = "0.2.15" 3608 + source = "registry+https://github.com/rust-lang/crates.io-index" 3609 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 3610 + 3611 + [[package]] 3612 + name = "version_check" 3613 + version = "0.9.5" 3614 + source = "registry+https://github.com/rust-lang/crates.io-index" 3615 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3616 + 3617 + [[package]] 3618 + name = "walkdir" 3619 + version = "2.5.0" 3620 + source = "registry+https://github.com/rust-lang/crates.io-index" 3621 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3622 + dependencies = [ 3623 + "same-file", 3624 + "winapi-util", 3625 + ] 3626 + 3627 + [[package]] 3628 + name = "want" 3629 + version = "0.3.1" 3630 + source = "registry+https://github.com/rust-lang/crates.io-index" 3631 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3632 + dependencies = [ 3633 + "try-lock", 3634 + ] 3635 + 3636 + [[package]] 3637 + name = "wasi" 3638 + version = "0.11.1+wasi-snapshot-preview1" 3639 + source = "registry+https://github.com/rust-lang/crates.io-index" 3640 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3641 + 3642 + [[package]] 3643 + name = "wasi" 3644 + version = "0.14.7+wasi-0.2.4" 3645 + source = "registry+https://github.com/rust-lang/crates.io-index" 3646 + checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" 3647 + dependencies = [ 3648 + "wasip2", 3649 + ] 3650 + 3651 + [[package]] 3652 + name = "wasip2" 3653 + version = "1.0.1+wasi-0.2.4" 3654 + source = "registry+https://github.com/rust-lang/crates.io-index" 3655 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 3656 + dependencies = [ 3657 + "wit-bindgen", 3658 + ] 3659 + 3660 + [[package]] 3661 + name = "wasm-bindgen" 3662 + version = "0.2.104" 3663 + source = "registry+https://github.com/rust-lang/crates.io-index" 3664 + checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" 3665 + dependencies = [ 3666 + "cfg-if", 3667 + "once_cell", 3668 + "rustversion", 3669 + "wasm-bindgen-macro", 3670 + "wasm-bindgen-shared", 3671 + ] 3672 + 3673 + [[package]] 3674 + name = "wasm-bindgen-backend" 3675 + version = "0.2.104" 3676 + source = "registry+https://github.com/rust-lang/crates.io-index" 3677 + checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" 3678 + dependencies = [ 3679 + "bumpalo", 3680 + "log", 3681 + "proc-macro2", 3682 + "quote", 3683 + "syn", 3684 + "wasm-bindgen-shared", 3685 + ] 3686 + 3687 + [[package]] 3688 + name = "wasm-bindgen-futures" 3689 + version = "0.4.54" 3690 + source = "registry+https://github.com/rust-lang/crates.io-index" 3691 + checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" 3692 + dependencies = [ 3693 + "cfg-if", 3694 + "js-sys", 3695 + "once_cell", 3696 + "wasm-bindgen", 3697 + "web-sys", 3698 + ] 3699 + 3700 + [[package]] 3701 + name = "wasm-bindgen-macro" 3702 + version = "0.2.104" 3703 + source = "registry+https://github.com/rust-lang/crates.io-index" 3704 + checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" 3705 + dependencies = [ 3706 + "quote", 3707 + "wasm-bindgen-macro-support", 3708 + ] 3709 + 3710 + [[package]] 3711 + name = "wasm-bindgen-macro-support" 3712 + version = "0.2.104" 3713 + source = "registry+https://github.com/rust-lang/crates.io-index" 3714 + checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" 3715 + dependencies = [ 3716 + "proc-macro2", 3717 + "quote", 3718 + "syn", 3719 + "wasm-bindgen-backend", 3720 + "wasm-bindgen-shared", 3721 + ] 3722 + 3723 + [[package]] 3724 + name = "wasm-bindgen-shared" 3725 + version = "0.2.104" 3726 + source = "registry+https://github.com/rust-lang/crates.io-index" 3727 + checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" 3728 + dependencies = [ 3729 + "unicode-ident", 3730 + ] 3731 + 3732 + [[package]] 3733 + name = "web-sys" 3734 + version = "0.3.81" 3735 + source = "registry+https://github.com/rust-lang/crates.io-index" 3736 + checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" 3737 + dependencies = [ 3738 + "js-sys", 3739 + "wasm-bindgen", 3740 + ] 3741 + 3742 + [[package]] 3743 + name = "widestring" 3744 + version = "1.2.0" 3745 + source = "registry+https://github.com/rust-lang/crates.io-index" 3746 + checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" 3747 + 3748 + [[package]] 3749 + name = "winapi" 3750 + version = "0.3.9" 3751 + source = "registry+https://github.com/rust-lang/crates.io-index" 3752 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3753 + dependencies = [ 3754 + "winapi-i686-pc-windows-gnu", 3755 + "winapi-x86_64-pc-windows-gnu", 3756 + ] 3757 + 3758 + [[package]] 3759 + name = "winapi-i686-pc-windows-gnu" 3760 + version = "0.4.0" 3761 + source = "registry+https://github.com/rust-lang/crates.io-index" 3762 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3763 + 3764 + [[package]] 3765 + name = "winapi-util" 3766 + version = "0.1.11" 3767 + source = "registry+https://github.com/rust-lang/crates.io-index" 3768 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 3769 + dependencies = [ 3770 + "windows-sys 0.61.0", 3771 + ] 3772 + 3773 + [[package]] 3774 + name = "winapi-x86_64-pc-windows-gnu" 3775 + version = "0.4.0" 3776 + source = "registry+https://github.com/rust-lang/crates.io-index" 3777 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3778 + 3779 + [[package]] 3780 + name = "windows-link" 3781 + version = "0.1.3" 3782 + source = "registry+https://github.com/rust-lang/crates.io-index" 3783 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3784 + 3785 + [[package]] 3786 + name = "windows-link" 3787 + version = "0.2.0" 3788 + source = "registry+https://github.com/rust-lang/crates.io-index" 3789 + checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 3790 + 3791 + [[package]] 3792 + name = "windows-registry" 3793 + version = "0.5.3" 3794 + source = "registry+https://github.com/rust-lang/crates.io-index" 3795 + checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 3796 + dependencies = [ 3797 + "windows-link 0.1.3", 3798 + "windows-result", 3799 + "windows-strings", 3800 + ] 3801 + 3802 + [[package]] 3803 + name = "windows-result" 3804 + version = "0.3.4" 3805 + source = "registry+https://github.com/rust-lang/crates.io-index" 3806 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3807 + dependencies = [ 3808 + "windows-link 0.1.3", 3809 + ] 3810 + 3811 + [[package]] 3812 + name = "windows-strings" 3813 + version = "0.4.2" 3814 + source = "registry+https://github.com/rust-lang/crates.io-index" 3815 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3816 + dependencies = [ 3817 + "windows-link 0.1.3", 3818 + ] 3819 + 3820 + [[package]] 3821 + name = "windows-sys" 3822 + version = "0.48.0" 3823 + source = "registry+https://github.com/rust-lang/crates.io-index" 3824 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3825 + dependencies = [ 3826 + "windows-targets 0.48.5", 3827 + ] 3828 + 3829 + [[package]] 3830 + name = "windows-sys" 3831 + version = "0.52.0" 3832 + source = "registry+https://github.com/rust-lang/crates.io-index" 3833 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3834 + dependencies = [ 3835 + "windows-targets 0.52.6", 3836 + ] 3837 + 3838 + [[package]] 3839 + name = "windows-sys" 3840 + version = "0.59.0" 3841 + source = "registry+https://github.com/rust-lang/crates.io-index" 3842 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 3843 + dependencies = [ 3844 + "windows-targets 0.52.6", 3845 + ] 3846 + 3847 + [[package]] 3848 + name = "windows-sys" 3849 + version = "0.60.2" 3850 + source = "registry+https://github.com/rust-lang/crates.io-index" 3851 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 3852 + dependencies = [ 3853 + "windows-targets 0.53.3", 3854 + ] 3855 + 3856 + [[package]] 3857 + name = "windows-sys" 3858 + version = "0.61.0" 3859 + source = "registry+https://github.com/rust-lang/crates.io-index" 3860 + checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" 3861 + dependencies = [ 3862 + "windows-link 0.2.0", 3863 + ] 3864 + 3865 + [[package]] 3866 + name = "windows-targets" 3867 + version = "0.48.5" 3868 + source = "registry+https://github.com/rust-lang/crates.io-index" 3869 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3870 + dependencies = [ 3871 + "windows_aarch64_gnullvm 0.48.5", 3872 + "windows_aarch64_msvc 0.48.5", 3873 + "windows_i686_gnu 0.48.5", 3874 + "windows_i686_msvc 0.48.5", 3875 + "windows_x86_64_gnu 0.48.5", 3876 + "windows_x86_64_gnullvm 0.48.5", 3877 + "windows_x86_64_msvc 0.48.5", 3878 + ] 3879 + 3880 + [[package]] 3881 + name = "windows-targets" 3882 + version = "0.52.6" 3883 + source = "registry+https://github.com/rust-lang/crates.io-index" 3884 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3885 + dependencies = [ 3886 + "windows_aarch64_gnullvm 0.52.6", 3887 + "windows_aarch64_msvc 0.52.6", 3888 + "windows_i686_gnu 0.52.6", 3889 + "windows_i686_gnullvm 0.52.6", 3890 + "windows_i686_msvc 0.52.6", 3891 + "windows_x86_64_gnu 0.52.6", 3892 + "windows_x86_64_gnullvm 0.52.6", 3893 + "windows_x86_64_msvc 0.52.6", 3894 + ] 3895 + 3896 + [[package]] 3897 + name = "windows-targets" 3898 + version = "0.53.3" 3899 + source = "registry+https://github.com/rust-lang/crates.io-index" 3900 + checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 3901 + dependencies = [ 3902 + "windows-link 0.1.3", 3903 + "windows_aarch64_gnullvm 0.53.0", 3904 + "windows_aarch64_msvc 0.53.0", 3905 + "windows_i686_gnu 0.53.0", 3906 + "windows_i686_gnullvm 0.53.0", 3907 + "windows_i686_msvc 0.53.0", 3908 + "windows_x86_64_gnu 0.53.0", 3909 + "windows_x86_64_gnullvm 0.53.0", 3910 + "windows_x86_64_msvc 0.53.0", 3911 + ] 3912 + 3913 + [[package]] 3914 + name = "windows_aarch64_gnullvm" 3915 + version = "0.48.5" 3916 + source = "registry+https://github.com/rust-lang/crates.io-index" 3917 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3918 + 3919 + [[package]] 3920 + name = "windows_aarch64_gnullvm" 3921 + version = "0.52.6" 3922 + source = "registry+https://github.com/rust-lang/crates.io-index" 3923 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3924 + 3925 + [[package]] 3926 + name = "windows_aarch64_gnullvm" 3927 + version = "0.53.0" 3928 + source = "registry+https://github.com/rust-lang/crates.io-index" 3929 + checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 3930 + 3931 + [[package]] 3932 + name = "windows_aarch64_msvc" 3933 + version = "0.48.5" 3934 + source = "registry+https://github.com/rust-lang/crates.io-index" 3935 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3936 + 3937 + [[package]] 3938 + name = "windows_aarch64_msvc" 3939 + version = "0.52.6" 3940 + source = "registry+https://github.com/rust-lang/crates.io-index" 3941 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3942 + 3943 + [[package]] 3944 + name = "windows_aarch64_msvc" 3945 + version = "0.53.0" 3946 + source = "registry+https://github.com/rust-lang/crates.io-index" 3947 + checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 3948 + 3949 + [[package]] 3950 + name = "windows_i686_gnu" 3951 + version = "0.48.5" 3952 + source = "registry+https://github.com/rust-lang/crates.io-index" 3953 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3954 + 3955 + [[package]] 3956 + name = "windows_i686_gnu" 3957 + version = "0.52.6" 3958 + source = "registry+https://github.com/rust-lang/crates.io-index" 3959 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3960 + 3961 + [[package]] 3962 + name = "windows_i686_gnu" 3963 + version = "0.53.0" 3964 + source = "registry+https://github.com/rust-lang/crates.io-index" 3965 + checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 3966 + 3967 + [[package]] 3968 + name = "windows_i686_gnullvm" 3969 + version = "0.52.6" 3970 + source = "registry+https://github.com/rust-lang/crates.io-index" 3971 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3972 + 3973 + [[package]] 3974 + name = "windows_i686_gnullvm" 3975 + version = "0.53.0" 3976 + source = "registry+https://github.com/rust-lang/crates.io-index" 3977 + checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 3978 + 3979 + [[package]] 3980 + name = "windows_i686_msvc" 3981 + version = "0.48.5" 3982 + source = "registry+https://github.com/rust-lang/crates.io-index" 3983 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3984 + 3985 + [[package]] 3986 + name = "windows_i686_msvc" 3987 + version = "0.52.6" 3988 + source = "registry+https://github.com/rust-lang/crates.io-index" 3989 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3990 + 3991 + [[package]] 3992 + name = "windows_i686_msvc" 3993 + version = "0.53.0" 3994 + source = "registry+https://github.com/rust-lang/crates.io-index" 3995 + checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 3996 + 3997 + [[package]] 3998 + name = "windows_x86_64_gnu" 3999 + version = "0.48.5" 4000 + source = "registry+https://github.com/rust-lang/crates.io-index" 4001 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 4002 + 4003 + [[package]] 4004 + name = "windows_x86_64_gnu" 4005 + version = "0.52.6" 4006 + source = "registry+https://github.com/rust-lang/crates.io-index" 4007 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 4008 + 4009 + [[package]] 4010 + name = "windows_x86_64_gnu" 4011 + version = "0.53.0" 4012 + source = "registry+https://github.com/rust-lang/crates.io-index" 4013 + checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 4014 + 4015 + [[package]] 4016 + name = "windows_x86_64_gnullvm" 4017 + version = "0.48.5" 4018 + source = "registry+https://github.com/rust-lang/crates.io-index" 4019 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 4020 + 4021 + [[package]] 4022 + name = "windows_x86_64_gnullvm" 4023 + version = "0.52.6" 4024 + source = "registry+https://github.com/rust-lang/crates.io-index" 4025 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4026 + 4027 + [[package]] 4028 + name = "windows_x86_64_gnullvm" 4029 + version = "0.53.0" 4030 + source = "registry+https://github.com/rust-lang/crates.io-index" 4031 + checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 4032 + 4033 + [[package]] 4034 + name = "windows_x86_64_msvc" 4035 + version = "0.48.5" 4036 + source = "registry+https://github.com/rust-lang/crates.io-index" 4037 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 4038 + 4039 + [[package]] 4040 + name = "windows_x86_64_msvc" 4041 + version = "0.52.6" 4042 + source = "registry+https://github.com/rust-lang/crates.io-index" 4043 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 4044 + 4045 + [[package]] 4046 + name = "windows_x86_64_msvc" 4047 + version = "0.53.0" 4048 + source = "registry+https://github.com/rust-lang/crates.io-index" 4049 + checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 4050 + 4051 + [[package]] 4052 + name = "winnow" 4053 + version = "0.7.13" 4054 + source = "registry+https://github.com/rust-lang/crates.io-index" 4055 + checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 4056 + dependencies = [ 4057 + "memchr", 4058 + ] 4059 + 4060 + [[package]] 4061 + name = "winreg" 4062 + version = "0.50.0" 4063 + source = "registry+https://github.com/rust-lang/crates.io-index" 4064 + checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 4065 + dependencies = [ 4066 + "cfg-if", 4067 + "windows-sys 0.48.0", 4068 + ] 4069 + 4070 + [[package]] 4071 + name = "wit-bindgen" 4072 + version = "0.46.0" 4073 + source = "registry+https://github.com/rust-lang/crates.io-index" 4074 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 4075 + 4076 + [[package]] 4077 + name = "writeable" 4078 + version = "0.6.1" 4079 + source = "registry+https://github.com/rust-lang/crates.io-index" 4080 + checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 4081 + 4082 + [[package]] 4083 + name = "xrpc" 4084 + version = "0.0.0" 4085 + dependencies = [ 4086 + "quote", 4087 + "syn", 4088 + ] 4089 + 4090 + [[package]] 4091 + name = "yoke" 4092 + version = "0.8.0" 4093 + source = "registry+https://github.com/rust-lang/crates.io-index" 4094 + checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 4095 + dependencies = [ 4096 + "serde", 4097 + "stable_deref_trait", 4098 + "yoke-derive", 4099 + "zerofrom", 4100 + ] 4101 + 4102 + [[package]] 4103 + name = "yoke-derive" 4104 + version = "0.8.0" 4105 + source = "registry+https://github.com/rust-lang/crates.io-index" 4106 + checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 4107 + dependencies = [ 4108 + "proc-macro2", 4109 + "quote", 4110 + "syn", 4111 + "synstructure", 4112 + ] 4113 + 4114 + [[package]] 4115 + name = "zerocopy" 4116 + version = "0.8.27" 4117 + source = "registry+https://github.com/rust-lang/crates.io-index" 4118 + checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" 4119 + dependencies = [ 4120 + "zerocopy-derive", 4121 + ] 4122 + 4123 + [[package]] 4124 + name = "zerocopy-derive" 4125 + version = "0.8.27" 4126 + source = "registry+https://github.com/rust-lang/crates.io-index" 4127 + checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" 4128 + dependencies = [ 4129 + "proc-macro2", 4130 + "quote", 4131 + "syn", 4132 + ] 4133 + 4134 + [[package]] 4135 + name = "zerofrom" 4136 + version = "0.1.6" 4137 + source = "registry+https://github.com/rust-lang/crates.io-index" 4138 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 4139 + dependencies = [ 4140 + "zerofrom-derive", 4141 + ] 4142 + 4143 + [[package]] 4144 + name = "zerofrom-derive" 4145 + version = "0.1.6" 4146 + source = "registry+https://github.com/rust-lang/crates.io-index" 4147 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 4148 + dependencies = [ 4149 + "proc-macro2", 4150 + "quote", 4151 + "syn", 4152 + "synstructure", 4153 + ] 4154 + 4155 + [[package]] 4156 + name = "zeroize" 4157 + version = "1.8.1" 4158 + source = "registry+https://github.com/rust-lang/crates.io-index" 4159 + checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 4160 + 4161 + [[package]] 4162 + name = "zerotrie" 4163 + version = "0.2.2" 4164 + source = "registry+https://github.com/rust-lang/crates.io-index" 4165 + checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 4166 + dependencies = [ 4167 + "displaydoc", 4168 + "yoke", 4169 + "zerofrom", 4170 + ] 4171 + 4172 + [[package]] 4173 + name = "zerovec" 4174 + version = "0.11.4" 4175 + source = "registry+https://github.com/rust-lang/crates.io-index" 4176 + checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 4177 + dependencies = [ 4178 + "yoke", 4179 + "zerofrom", 4180 + "zerovec-derive", 4181 + ] 4182 + 4183 + [[package]] 4184 + name = "zerovec-derive" 4185 + version = "0.11.1" 4186 + source = "registry+https://github.com/rust-lang/crates.io-index" 4187 + checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 4188 + dependencies = [ 4189 + "proc-macro2", 4190 + "quote", 4191 + "syn", 4192 + ] 4193 + 4194 + [[package]] 4195 + name = "zlib-rs" 4196 + version = "0.5.2" 4197 + source = "registry+https://github.com/rust-lang/crates.io-index" 4198 + checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
+27
Cargo.toml
··· 1 + [workspace] 2 + resolver = "3" 3 + members = ["crates/knot", "crates/identity", "crates/service_auth", "crates/xrpc"] 4 + default-members = ["crates/knot"] 5 + 6 + [workspace.package] 7 + version = "0.0.0" 8 + authors = ["tjh <did:plc:65gha4t3avpfpzmvpbwovss7>"] 9 + repository = "https://tangled.org/@tjh.dev/gordian" 10 + license = "MIT or Apache-2.0" 11 + edition = "2024" 12 + publish = false 13 + 14 + [workspace.dependencies] 15 + identity = { path = "crates/identity" } 16 + service_auth = { path = "crates/service_auth" } 17 + xrpc = { path = "crates/xrpc" } 18 + knot = { path = "crates/knot" } 19 + axum = "0.8.4" 20 + serde = "1.0.226" 21 + thiserror = "2.0.16" 22 + tracing = "0.1.41" 23 + 24 + [profile.release] 25 + lto = "fat" 26 + strip = true 27 +
+17
contrib/gordian-knot.service
··· 1 + [Unit] 2 + Description=Gordian Knot server 3 + After=network.target network-online.target 4 + Wants=network-online.target 5 + AssertPathExists=/var/lib/tangled 6 + 7 + [Service] 8 + Environment=KNOT_SERVER_BASE=/var/lib/tangled 9 + Environment=KNOT_SERVER_UPSTREAM=5555 10 + WorkingDirectory=/var/lib/tangled 11 + ExecStart=/usr/bin/gordian-knot 12 + Restart=always 13 + User=git 14 + Group=git 15 + 16 + [Install] 17 + WantedBy=multi-user.target
+19
crates/identity/Cargo.toml
··· 1 + [package] 2 + name = "identity" 3 + version.workspace = true 4 + authors.workspace = true 5 + edition.workspace = true 6 + publish.workspace = true 7 + 8 + [dependencies] 9 + async-trait = "0.1.89" 10 + hickory-resolver = "0.25.2" 11 + moka = "0.12.11" 12 + reqwest = { version = "0.12.23", features = ["json"] } 13 + serde = { workspace = true, features = ["derive"] } 14 + serde_json = { version = "1.0.145" } 15 + thiserror = "2.0.16" 16 + tokio = { version = "1.47.1", features = ["rt"] } 17 + tracing.workspace = true 18 + tracing-subscriber = "0.3.20" 19 + url = { version = "2.5.7", features = ["serde"] }
+212
crates/identity/src/did.rs
··· 1 + use serde::{Deserialize, Serialize, de::Visitor}; 2 + use std::{path::Path, str::FromStr}; 3 + 4 + /// A Decentralized Identifier. 5 + #[derive(Clone)] 6 + pub struct Did { 7 + // Byte offset to the start of the ident section of the DID. 8 + off: u16, 9 + src: Box<str>, 10 + } 11 + 12 + #[derive(Debug, thiserror::Error)] 13 + pub enum Error { 14 + #[error("Expected a DID in the form \"did:{{method}}:{{ident}}\"")] 15 + Format, 16 + #[error("Invalid DID type, expected \"did\"")] 17 + Type, 18 + #[error("Invalid DID method")] 19 + Method, 20 + #[error("Invalid DID identifier")] 21 + Ident, 22 + } 23 + 24 + impl Did { 25 + /// Create a new `Did` from a static str. 26 + /// 27 + /// Panics if `s` is not a valid DID. 28 + #[inline] 29 + pub fn from_static(s: &'static str) -> Self { 30 + Self::from_str(s).unwrap() 31 + } 32 + 33 + /// DID type. Always returns `"did"`. 34 + #[inline] 35 + pub const fn typ(&self) -> &'static str { 36 + "did" 37 + } 38 + 39 + /// DID method. 40 + #[inline] 41 + pub fn method(&self) -> &str { 42 + &self.src[4..usize::from(self.off) - 1] 43 + } 44 + 45 + /// DID identifier. 46 + #[inline] 47 + pub fn ident(&self) -> &str { 48 + &self.src[usize::from(self.off)..] 49 + } 50 + 51 + #[inline] 52 + pub const fn as_str(&self) -> &str { 53 + &self.src 54 + } 55 + } 56 + 57 + impl AsRef<str> for Did { 58 + #[inline] 59 + fn as_ref(&self) -> &str { 60 + Did::as_str(self) 61 + } 62 + } 63 + 64 + impl AsRef<Path> for Did { 65 + #[inline] 66 + fn as_ref(&self) -> &Path { 67 + Did::as_str(self).as_ref() 68 + } 69 + } 70 + 71 + impl AsRef<[u8]> for Did { 72 + #[inline] 73 + fn as_ref(&self) -> &[u8] { 74 + Did::as_str(self).as_bytes() 75 + } 76 + } 77 + 78 + impl FromStr for Did { 79 + type Err = Error; 80 + 81 + fn from_str(s: &str) -> Result<Self, Self::Err> { 82 + let mut parts = s.splitn(3, ':'); 83 + match (parts.next(), parts.next(), parts.next()) { 84 + (Some("did"), Some(method), Some(ident)) => { 85 + if method.is_empty() { 86 + return Err(Error::Method); 87 + } 88 + 89 + if ident.ends_with(':') { 90 + return Err(Error::Ident); 91 + } 92 + 93 + let offset = (5 + method.len()).try_into().map_err(|_| Error::Format)?; 94 + 95 + let buffer = s.into(); 96 + 97 + Ok(Self { 98 + off: offset, 99 + src: buffer, 100 + }) 101 + } 102 + (Some(_), Some(_), Some(_)) => Err(Error::Type), 103 + _ => Err(Error::Format), 104 + } 105 + } 106 + } 107 + 108 + impl PartialEq<Did> for Did { 109 + #[inline] 110 + fn eq(&self, other: &Did) -> bool { 111 + self.src.eq(&other.src) 112 + } 113 + } 114 + 115 + impl Eq for Did {} 116 + 117 + impl std::hash::Hash for Did { 118 + #[inline] 119 + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { 120 + state.write(self.src.as_bytes()) 121 + } 122 + } 123 + 124 + impl std::fmt::Debug for Did { 125 + #[inline] 126 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 127 + f.write_str(&self.src) 128 + } 129 + } 130 + 131 + impl std::fmt::Display for Did { 132 + #[inline] 133 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 134 + f.write_str(&self.src) 135 + } 136 + } 137 + 138 + impl<'de> Deserialize<'de> for Did { 139 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 140 + where 141 + D: serde::Deserializer<'de>, 142 + { 143 + struct DidVisitor; 144 + 145 + impl<'de> Visitor<'de> for DidVisitor { 146 + type Value = Did; 147 + 148 + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 149 + formatter.write_str("did") 150 + } 151 + 152 + #[inline] 153 + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 154 + where 155 + E: serde::de::Error, 156 + { 157 + Did::from_str(v).map_err(serde::de::Error::custom) 158 + } 159 + } 160 + 161 + deserializer.deserialize_str(DidVisitor) 162 + } 163 + } 164 + 165 + impl Serialize for Did { 166 + #[inline] 167 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 168 + where 169 + S: serde::Serializer, 170 + { 171 + serializer.serialize_str(&self.src) 172 + } 173 + } 174 + 175 + #[cfg(test)] 176 + mod tests { 177 + use super::Did; 178 + use std::str::FromStr; 179 + 180 + #[test] 181 + fn parse_valid() { 182 + let did = Did::from_str("did:plc:65gha4t3avpfpzmvpbwovss7").unwrap(); 183 + assert_eq!(did.typ(), "did"); 184 + assert_eq!(did.method(), "plc"); 185 + assert_eq!(did.ident(), "65gha4t3avpfpzmvpbwovss7"); 186 + 187 + let did = Did::from_str("did:web:tjh.dev").unwrap(); 188 + assert_eq!(did.typ(), "did"); 189 + assert_eq!(did.method(), "web"); 190 + assert_eq!(did.ident(), "tjh.dev"); 191 + } 192 + 193 + #[test] 194 + fn reject_wrong_type() { 195 + assert!(Did::from_str("dod:plc:asdf").is_err()); 196 + } 197 + 198 + #[test] 199 + fn reject_empty_type() { 200 + assert!(Did::from_str(":plc:65gha4t3avpfpzmvpbwovss7").is_err()); 201 + } 202 + 203 + #[test] 204 + fn reject_empty_method() { 205 + assert!(Did::from_str("did::65gha4t3avpfpzmvpbwovss7").is_err()); 206 + } 207 + 208 + #[test] 209 + fn reject_trailing_colon() { 210 + assert!(Did::from_str("did:plc:65gha4t3avpfpzmvpbwovss7:").is_err()); 211 + } 212 + }
+53
crates/identity/src/document.rs
··· 1 + use crate::Did; 2 + use serde::{Deserialize, Serialize}; 3 + use std::collections::HashMap; 4 + use url::Url; 5 + 6 + #[derive(Clone, Debug, Deserialize, Serialize)] 7 + #[serde(tag = "type", rename_all = "PascalCase")] 8 + pub enum VerificationMethod { 9 + #[serde(rename_all = "camelCase")] 10 + Multikey { 11 + id: Did, 12 + controller: Did, 13 + public_key_multibase: String, 14 + }, 15 + } 16 + 17 + #[derive(Clone, Debug, Deserialize, Serialize)] 18 + #[serde(rename_all = "camelCase")] 19 + pub struct Service { 20 + pub id: String, 21 + #[serde(rename = "type")] 22 + pub typ: String, 23 + pub service_endpoint: Url, 24 + } 25 + 26 + #[derive(Clone, Debug, Deserialize, Serialize)] 27 + #[serde(rename_all = "camelCase")] 28 + pub struct DidDocument { 29 + #[serde(rename = "@context")] 30 + pub context: Vec<Url>, 31 + pub id: Did, 32 + pub also_known_as: Vec<Url>, 33 + pub verification_method: Vec<VerificationMethod>, 34 + pub service: Vec<Service>, 35 + } 36 + 37 + #[derive(Clone, Debug, Deserialize, Serialize)] 38 + #[serde(rename_all = "camelCase")] 39 + pub struct ServiceData { 40 + #[serde(rename = "type")] 41 + pub typ: String, 42 + pub endpoint: Url, 43 + } 44 + 45 + #[derive(Clone, Debug, Deserialize, Serialize)] 46 + #[serde(rename_all = "camelCase")] 47 + pub struct DidDocumentData { 48 + pub did: Did, 49 + pub verification_methods: HashMap<String, Did>, 50 + pub rotation_keys: Vec<Did>, 51 + pub also_known_as: Vec<Url>, 52 + pub services: HashMap<String, ServiceData>, 53 + }
+100
crates/identity/src/handles.rs
··· 1 + pub const DISALLOWED_TLDS: &[&str] = &[ 2 + "alt", 3 + "arpa", 4 + "example", 5 + "internal", 6 + "invalid", 7 + "local", 8 + "localhost", 9 + "onion", 10 + #[cfg(not(test))] 11 + "test", 12 + ]; 13 + 14 + #[derive(Debug, thiserror::Error)] 15 + #[error("Handle is invalid")] 16 + pub struct InvalidHandle; 17 + 18 + pub fn validate_handle(handle: &str) -> Result<(), InvalidHandle> { 19 + if handle.is_empty() || handle.len() > 253 { 20 + return Err(InvalidHandle); 21 + } 22 + 23 + let mut last = ""; 24 + let mut segments = 0; 25 + for segment in handle.split('.') { 26 + segments += 1; 27 + if segment.is_empty() 28 + || segment.len() > 63 29 + || segment.starts_with('-') 30 + || segment.ends_with('-') 31 + || !segment 32 + .chars() 33 + .all(|c| c.is_ascii_alphanumeric() || c == '-') 34 + { 35 + return Err(InvalidHandle); 36 + } 37 + 38 + last = segment; 39 + } 40 + 41 + if segments < 2 { 42 + return Err(InvalidHandle); 43 + } 44 + 45 + if DISALLOWED_TLDS.contains(&last) || last.starts_with(|c: char| c.is_ascii_digit()) { 46 + return Err(InvalidHandle); 47 + } 48 + 49 + Ok(()) 50 + } 51 + 52 + #[cfg(test)] 53 + mod tests { 54 + use super::validate_handle; 55 + 56 + #[test] 57 + fn accept_valid() { 58 + for handle in [ 59 + "jay.bsky.social", 60 + "8.cn", 61 + "name.t--t", 62 + "XX.LCS.MIT.EDU", 63 + "a.co", 64 + "xn--notarealidn.com", 65 + "xn--fiqa61au8b7zsevnm8ak20mc4a87e.xn--fiqs8s", 66 + "xn--ls8h.test", 67 + "example.t", 68 + ] { 69 + assert!(validate_handle(handle).is_ok(), "{handle} is invalid"); 70 + } 71 + } 72 + 73 + #[test] 74 + fn reject_invalid_syntax() { 75 + for handle in [ 76 + "jo@hn.test", 77 + "💩.test", 78 + "john..test", 79 + "xn--bcher-.tld", 80 + "john.0", 81 + "cn.8", 82 + "www.masełkowski.pl.com", 83 + "org", 84 + "name.org.", 85 + ] { 86 + assert!(validate_handle(handle).is_err(), "{handle} is valid"); 87 + } 88 + } 89 + 90 + #[test] 91 + fn reject_restricted() { 92 + for handle in [ 93 + "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion", 94 + "laptop.local", 95 + "blah.arpa", 96 + ] { 97 + assert!(validate_handle(handle).is_err(), "{handle} is valid"); 98 + } 99 + } 100 + }
+71
crates/identity/src/lib.rs
··· 1 + mod did; 2 + mod document; 3 + pub mod handles; 4 + mod resolvers; 5 + 6 + pub use did::Did; 7 + pub use document::*; 8 + 9 + use std::sync::Arc; 10 + 11 + pub const DEFAULT_PLC: &str = "https://plc.directory"; 12 + 13 + #[async_trait::async_trait] 14 + trait HandleResolve: std::fmt::Debug { 15 + /// Resolve a handle to a DID. 16 + /// 17 + /// Related: <https://docs.bsky.app/docs/api/com-atproto-identity-resolve-handle> 18 + async fn resolve_handle(&self, handle: &str) -> Result<Did, ResolveError>; 19 + 20 + /// Resolve a DID to DID document. 21 + /// 22 + /// Related: <https://docs.bsky.app/docs/api/com-atproto-identity-resolve-did> 23 + async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError>; 24 + } 25 + 26 + #[derive(Debug, thiserror::Error)] 27 + #[error("Failed to resolve something")] 28 + pub struct ResolveError; 29 + 30 + impl ResolveError { 31 + pub fn not_found() -> Self { 32 + Self 33 + } 34 + } 35 + 36 + #[derive(Clone, Debug)] 37 + pub struct Resolver { 38 + inner: Arc<dyn HandleResolve + Sync + Send + 'static>, 39 + } 40 + 41 + impl Resolver { 42 + /// Resolves an ATproto handle to a DID document. 43 + /// 44 + /// The resolved DID is always bidirectionally confirmed. 45 + pub async fn resolve_handle(&self, handle: &str) -> Result<(Did, DidDocument), ResolveError> { 46 + let handle = handle.trim_start_matches('@'); 47 + let did = self.inner.resolve_handle(handle).await?; 48 + let doc = self.inner.resolve_did(&did).await?; 49 + Ok((did, doc)) 50 + } 51 + 52 + #[inline] 53 + pub async fn resolve_did(&self, handle: &str) -> Result<Did, ResolveError> { 54 + let handle = handle.trim_start_matches('@'); 55 + self.inner.resolve_handle(handle).await 56 + } 57 + 58 + #[inline] 59 + pub async fn resolve_identity(&self, did: &Did) -> Result<DidDocument, ResolveError> { 60 + self.inner.resolve_did(did).await 61 + } 62 + } 63 + 64 + impl Default for Resolver { 65 + fn default() -> Self { 66 + let inner = resolvers::MemcachedResolver::default(); 67 + Self { 68 + inner: Arc::new(inner), 69 + } 70 + } 71 + }
+18
crates/identity/src/main.rs
··· 1 + use identity::Resolver; 2 + 3 + #[tokio::main(flavor = "current_thread")] 4 + async fn main() { 5 + tracing_subscriber::fmt::init(); 6 + 7 + let resolver = Resolver::default(); 8 + for handle in std::env::args().skip(1) { 9 + match resolver.resolve_handle(&handle).await { 10 + Ok((_, document)) => { 11 + let pretty = serde_json::to_string_pretty(&document) 12 + .expect("Failed to serialize deserialized document"); 13 + println!("{handle}: {pretty}"); 14 + } 15 + Err(error) => eprintln!("{handle}: {error}"), 16 + }; 17 + } 18 + }
+232
crates/identity/src/resolvers.rs
··· 1 + use crate::{DEFAULT_PLC, Did, ResolveError, document::DidDocument}; 2 + pub use hickory_resolver::name_server::ConnectionProvider; 3 + use hickory_resolver::{ 4 + ResolveError as DnsResolveError, Resolver as DnsClient, TokioResolver, 5 + name_server::TokioConnectionProvider, 6 + }; 7 + use moka::sync::{Cache, CacheBuilder}; 8 + use reqwest::Client as HttpClient; 9 + use std::{borrow::Cow, str::FromStr, time::Duration}; 10 + use tokio::time::Instant; 11 + 12 + #[derive(Debug)] 13 + pub struct MemcachedResolver { 14 + did_cache: Cache<Box<str>, Did>, 15 + doc_cache: Cache<Did, DidDocument>, 16 + inner: DirectResolver<'static, TokioConnectionProvider>, // 17 + } 18 + 19 + #[async_trait::async_trait] 20 + impl super::HandleResolve for MemcachedResolver { 21 + // async fn resolve(&self, handle: &str) -> Result<Arc<(Did, DidDocument)>, ResolveError> { 22 + // if let Some(cached) = self.did_cache.get(handle) { 23 + // tracing::debug!(?handle, "reusing resolved did & document from cache"); 24 + // return Ok(cached); 25 + // } 26 + 27 + // let resolved = Arc::new(self.inner.resolve(handle).await?); 28 + // self.cache.insert(handle.into(), Arc::clone(&resolved)); 29 + 30 + // Ok(resolved) 31 + // } 32 + 33 + async fn resolve_handle(&self, handle: &str) -> Result<Did, ResolveError> { 34 + if let Some(did) = self.did_cache.get(handle) { 35 + tracing::debug!(?handle, ?did, "reusing resolved did from cache"); 36 + return Ok(did); 37 + } 38 + 39 + let (did, doc) = self.inner.resolve(handle).await?; 40 + self.did_cache.insert(handle.into(), did.clone()); 41 + self.doc_cache.insert(did.clone(), doc); 42 + 43 + Ok(did.clone()) 44 + } 45 + 46 + async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError> { 47 + if let Some(doc) = self.doc_cache.get(did) { 48 + return Ok(doc); 49 + } 50 + 51 + let doc = self.inner.fetch_did_document(did).await?; 52 + 53 + // @TODO Verify handle(s) in DID Document resolve to the DID. 54 + 55 + self.doc_cache.insert(did.clone(), doc.clone()); 56 + Ok(doc) 57 + } 58 + } 59 + 60 + impl Default for MemcachedResolver { 61 + fn default() -> Self { 62 + let did_cache = CacheBuilder::new(1000) 63 + .time_to_live(Duration::from_secs(600)) 64 + .build(); 65 + 66 + let doc_cache = CacheBuilder::new(1000) 67 + .time_to_live(Duration::from_secs(600)) 68 + .build(); 69 + 70 + Self { 71 + did_cache, 72 + doc_cache, 73 + inner: DirectResolver::default(), 74 + } 75 + } 76 + } 77 + 78 + pub struct DirectResolver<'plc, R: ConnectionProvider> { 79 + directory: Cow<'plc, str>, 80 + dns: DnsClient<R>, 81 + http: HttpClient, 82 + } 83 + 84 + impl<'a, R: ConnectionProvider> std::fmt::Debug for DirectResolver<'a, R> { 85 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 + f.debug_struct("DirectResolver") 87 + .field("directory", &self.directory) 88 + .finish_non_exhaustive() 89 + } 90 + } 91 + 92 + impl<'plc, R: ConnectionProvider> DirectResolver<'plc, R> { 93 + pub async fn resolve(&self, handle: &str) -> Result<(Did, DidDocument), ResolveError> { 94 + let did = self.resolve_handle(handle).await?; 95 + let Ok(document) = self.fetch_did_document(&did).await else { 96 + return Err(ResolveError); 97 + }; 98 + 99 + // Verify the document has a matching handle. 100 + for alias in &document.also_known_as { 101 + if alias.host_str().is_some_and(|host| host == handle) { 102 + return Ok((did, document)); 103 + } 104 + } 105 + 106 + Err(ResolveError) 107 + } 108 + 109 + /// Resolves an ATproto handle to a DID. 110 + /// 111 + /// Resolution is attempted via DNS and HTTP .well-known methods. The *first* 112 + /// method to return a valid result is used. 113 + pub async fn resolve_handle(&self, handle: &str) -> Result<Did, ResolveError> { 114 + let dns = resolve_handle_dns(&self.dns, handle); 115 + let http = resolve_handle_http(&self.http, handle); 116 + 117 + let start = Instant::now(); 118 + tokio::select! { 119 + Ok(Some(did)) = dns => { 120 + tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via dns"); 121 + Ok(did) 122 + }, 123 + Ok(Some(did)) = http => { 124 + tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via http"); 125 + Ok(did) 126 + } 127 + else => Err(ResolveError::not_found()), 128 + } 129 + } 130 + 131 + /// Fetches and parses the DID document. 132 + pub async fn fetch_did_document(&self, did: &Did) -> Result<DidDocument, ResolveError> { 133 + match did.method() { 134 + "plc" => self 135 + .fetch_plc_did_document(did) 136 + .await 137 + .map_err(|_| ResolveError), 138 + "web" => self 139 + .fetch_web_did_document(did) 140 + .await 141 + .map_err(|_| ResolveError), 142 + _ => unimplemented!(), 143 + } 144 + } 145 + 146 + pub async fn fetch_plc_did_document(&self, did: &Did) -> Result<DidDocument, reqwest::Error> { 147 + self.http 148 + .get(format!("{}/{did}", self.directory)) 149 + .send() 150 + .await? 151 + .error_for_status()? 152 + .json() 153 + .await 154 + } 155 + 156 + pub async fn fetch_web_did_document(&self, did: &Did) -> Result<DidDocument, reqwest::Error> { 157 + self.http 158 + .get(format!("https://{}/.well-known/did.json", did.ident())) 159 + .send() 160 + .await? 161 + .error_for_status()? 162 + .json() 163 + .await 164 + } 165 + } 166 + 167 + impl Default for DirectResolver<'static, TokioConnectionProvider> { 168 + fn default() -> Self { 169 + Self { 170 + directory: Cow::Borrowed(DEFAULT_PLC), 171 + dns: TokioResolver::builder_tokio() 172 + .expect("Failed to build default DNS resolver") 173 + .build(), 174 + http: HttpClient::new(), 175 + } 176 + } 177 + } 178 + 179 + pub async fn resolve_handle_dns<R>( 180 + client: &DnsClient<R>, 181 + handle: &str, 182 + ) -> Result<Option<Did>, DnsResolveError> 183 + where 184 + R: ConnectionProvider, 185 + { 186 + let mut resolved_did = None; 187 + let txt_lookup = client.txt_lookup(format!("_atproto.{handle}.")).await?; 188 + for record in txt_lookup.iter() { 189 + for txt_data in record.txt_data() { 190 + let Ok(txt) = std::str::from_utf8(txt_data) else { 191 + continue; 192 + }; 193 + let Some(txt_did) = txt.strip_prefix("did=") else { 194 + continue; 195 + }; 196 + let Ok(did) = Did::from_str(txt_did) else { 197 + continue; 198 + }; 199 + 200 + if let Some(old_did) = resolved_did.replace(did.clone()) 201 + && old_did != did 202 + { 203 + tracing::error!( 204 + ?handle, 205 + ?did, 206 + ?old_did, 207 + "multiple conflicting DIDs found for handle" 208 + ); 209 + // @TODO Replace this with an error so we can retry with a 210 + // recursive dns resolver. 211 + return Ok(None); 212 + } 213 + } 214 + } 215 + 216 + Ok(resolved_did) 217 + } 218 + 219 + pub async fn resolve_handle_http( 220 + client: &HttpClient, 221 + handle: &str, 222 + ) -> Result<Option<Did>, reqwest::Error> { 223 + let response = client 224 + .get(format!("https://{handle}/.well-known/atproto-did")) 225 + .send() 226 + .await? 227 + .error_for_status()? 228 + .text() 229 + .await?; 230 + 231 + Ok(Did::from_str(&response).ok()) 232 + }
+35
crates/knot/Cargo.toml
··· 1 + [package] 2 + name = "knot" 3 + version.workspace = true 4 + edition.workspace = true 5 + authors.workspace = true 6 + publish.workspace = true 7 + 8 + [dependencies] 9 + anyhow = "1.0.99" 10 + axum = { workspace = true, features = ["ws"] } 11 + axum-extra = { version = "0.10.1", features = ["async-read-body"] } 12 + bytes = "1.10.1" 13 + clap = { version = "4.5.47", features = ["derive", "env", "string"] } 14 + data-encoding = "2.9.0" 15 + gix = { version = "0.73.0", features = ["max-performance"] } 16 + hyper-util = { version = "0.1.17", features = ["client"] } 17 + identity.workspace = true 18 + reqwest = { version = "0.12.23", features = ["json"] } 19 + rustc-hash = "2.1.1" 20 + serde = { workspace = true, features = ["derive", "rc"] } 21 + serde_json = { version = "1.0.145" } 22 + thiserror = "2.0.16" 23 + time = { version = "0.3.43", features = ["formatting", "macros", "parsing", "serde"] } 24 + tokio = { version = "1.47.1", features = ["io-util", "macros", "net", "process", "signal", "rt-multi-thread"] } 25 + tower = "0.5.2" 26 + tower-http = { version = "0.6.6", features = ["decompression-gzip", "request-id", "trace", "tracing"] } 27 + tracing.workspace = true 28 + tracing-journald = "0.3.1" 29 + tracing-subscriber = "0.3.20" 30 + url = { version = "2.5.7", features = ["serde"] } 31 + xrpc.workspace = true 32 + 33 + [[bin]] 34 + name = "gordian-knot" 35 + path = "src/main.rs"
+68
crates/knot/src/cli.rs
··· 1 + use clap::Parser; 2 + use identity::{Did, ResolveError, Resolver}; 3 + use std::{path::PathBuf, str::FromStr}; 4 + 5 + #[derive(Debug, Parser)] 6 + pub struct Arguments { 7 + /// Address(s) to bind the public API. 8 + #[arg( 9 + long, 10 + short, 11 + env = "KNOT_SERVER_ADDR", 12 + default_value = "localhost:5556" 13 + )] 14 + pub addr: Vec<String>, 15 + 16 + /// DID or handle of the knot owner. 17 + #[arg(long, env = "KNOT_SERVER_OWNER")] 18 + pub owner: HandleOrDid, 19 + 20 + /// Base directory to serve repositories from. 21 + #[arg(long, env = "KNOT_SERVER_BASE", default_value = default_repository_base().into_os_string())] 22 + pub repos: PathBuf, 23 + 24 + /// Port number for the real knotserver. 25 + #[arg(long, env = "KNOT_SERVER_UPSTREAM")] 26 + pub upstream_port: Option<u16>, 27 + } 28 + 29 + fn default_repository_base() -> PathBuf { 30 + std::env::current_dir().unwrap_or_default() 31 + } 32 + 33 + pub fn parse() -> Arguments { 34 + Arguments::parse() 35 + } 36 + 37 + #[derive(Clone, Debug)] 38 + pub enum HandleOrDid { 39 + Did(Did), 40 + Handle(String), 41 + } 42 + 43 + impl HandleOrDid { 44 + pub async fn into_did(self, resolver: &Resolver) -> Result<Did, ResolveError> { 45 + match self { 46 + Self::Did(did) => Ok(did), 47 + Self::Handle(handle) => { 48 + let did = resolver.resolve_did(&handle).await?; 49 + tracing::info!(?handle, ?did, "resolved owner"); 50 + Ok(did) 51 + } 52 + } 53 + } 54 + } 55 + 56 + impl FromStr for HandleOrDid { 57 + type Err = anyhow::Error; 58 + 59 + fn from_str(s: &str) -> Result<Self, Self::Err> { 60 + if let Ok(did) = Did::from_str(s) { 61 + return Ok(Self::Did(did)); 62 + } 63 + 64 + // @TODO Check the handle is valid before trying to resolve it. 65 + let maybe_handle = s.trim_start_matches('@'); 66 + Ok(Self::Handle(maybe_handle.to_owned())) 67 + } 68 + }
+17
crates/knot/src/convert.rs
··· 1 + //! 2 + //! Utility conversions. 3 + //! 4 + use gix::date::Time; 5 + use time::OffsetDateTime; 6 + use time::UtcOffset; 7 + use time::error::ComponentRange; 8 + 9 + /// Convert a [`gix::date::Time`] to a [`time::OffsetDateTime`]. 10 + /// 11 + #[allow(unused)] 12 + pub fn time_to_offsetdatetime(time: &Time) -> Result<OffsetDateTime, ComponentRange> { 13 + let odt = OffsetDateTime::from_unix_timestamp(time.seconds)? 14 + .to_offset(UtcOffset::from_whole_seconds(time.offset)?); 15 + 16 + Ok(odt) 17 + }
+125
crates/knot/src/hex_object_id.rs
··· 1 + use data_encoding::Encoding; 2 + use data_encoding::HEXLOWER_PERMISSIVE; 3 + use serde::Deserialize; 4 + use serde::Deserializer; 5 + use serde::Serializer; 6 + use serde::de::Error; 7 + 8 + const ENCODING: Encoding = HEXLOWER_PERMISSIVE; 9 + 10 + pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> 11 + where 12 + S: Serializer, 13 + { 14 + let encoded = ENCODING.encode(bytes); 15 + serializer.serialize_str(&encoded) 16 + } 17 + 18 + pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error> 19 + where 20 + D: Deserializer<'de>, 21 + { 22 + let encoded = <&[u8] as Deserialize>::deserialize(deserializer)?; 23 + let decoded = ENCODING.decode(encoded).map_err(Error::custom)?; 24 + Ok(decoded) 25 + } 26 + 27 + pub mod vec { 28 + use super::ENCODING; 29 + use serde::Deserializer; 30 + use serde::Serializer; 31 + use serde::de::Visitor; 32 + use serde::ser::SerializeSeq; 33 + 34 + pub fn serialize<S>(hashes: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error> 35 + where 36 + S: Serializer, 37 + { 38 + let mut seq = serializer.serialize_seq(Some(hashes.len()))?; 39 + for hash in hashes { 40 + let encoded = ENCODING.encode(hash); 41 + seq.serialize_element(&encoded)?; 42 + } 43 + seq.end() 44 + } 45 + 46 + struct VecVisitor; 47 + 48 + impl<'de> Visitor<'de> for VecVisitor { 49 + type Value = Vec<Vec<u8>>; 50 + 51 + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 52 + formatter.write_str("an sequence of commit hashes") 53 + } 54 + 55 + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 56 + where 57 + A: serde::de::SeqAccess<'de>, 58 + { 59 + while let Some(value) = seq.next_element::<&str>()? { 60 + eprintln!("{value}"); 61 + // 62 + } 63 + 64 + Ok(Vec::new()) 65 + } 66 + } 67 + 68 + pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error> 69 + where 70 + D: Deserializer<'de>, 71 + { 72 + deserializer.deserialize_seq(VecVisitor) 73 + } 74 + } 75 + 76 + // pub mod smallvec { 77 + // use super::super::ObjectId; 78 + // use super::ENCODING; 79 + // use serde::Deserializer; 80 + // use serde::Serializer; 81 + // use serde::de::Visitor; 82 + // use serde::ser::SerializeSeq; 83 + // use smallvec::SmallVec; 84 + 85 + // pub fn serialize<S>(hashes: &[ObjectId], serializer: S) -> Result<S::Ok, S::Error> 86 + // where 87 + // S: Serializer, 88 + // { 89 + // let mut seq = serializer.serialize_seq(Some(hashes.len()))?; 90 + // for hash in hashes { 91 + // let encoded = ENCODING.encode(hash); 92 + // seq.serialize_element(&encoded)?; 93 + // } 94 + // seq.end() 95 + // } 96 + 97 + // struct VecVisitor; 98 + 99 + // impl<'de> Visitor<'de> for VecVisitor { 100 + // type Value = SmallVec<[ObjectId; 1]>; 101 + 102 + // fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 103 + // formatter.write_str("an sequence of commit hashes") 104 + // } 105 + 106 + // fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 107 + // where 108 + // A: serde::de::SeqAccess<'de>, 109 + // { 110 + // while let Some(value) = seq.next_element::<&str>()? { 111 + // eprintln!("{value}"); 112 + // // 113 + // } 114 + 115 + // Ok(SmallVec::new()) 116 + // } 117 + // } 118 + 119 + // pub fn deserialize<'de, D>(deserializer: D) -> Result<SmallVec<[ObjectId; 1]>, D::Error> 120 + // where 121 + // D: Deserializer<'de>, 122 + // { 123 + // deserializer.deserialize_seq(VecVisitor) 124 + // } 125 + // }
+10
crates/knot/src/lib.rs
··· 1 + pub(crate) mod convert; 2 + pub mod model; 3 + pub mod public; 4 + pub mod types; 5 + 6 + mod objectid; 7 + pub use objectid::ObjectId; 8 + 9 + mod repoid; 10 + pub use repoid::RepoId;
+153
crates/knot/src/main.rs
··· 1 + use axum::{ 2 + Router, 3 + extract::Query, 4 + http::{HeaderName, Request, Response, Uri}, 5 + response::IntoResponse, 6 + }; 7 + use hyper_util::{ 8 + client::legacy::{Client, connect::HttpConnector}, 9 + rt::TokioExecutor, 10 + }; 11 + use identity::Resolver; 12 + use knot::{RepoId, model::Knot}; 13 + use reqwest::StatusCode; 14 + use serde::Deserialize; 15 + use std::{ 16 + net::{SocketAddr, ToSocketAddrs}, 17 + time::Duration, 18 + }; 19 + use tokio::runtime::Builder; 20 + use tokio::{net::TcpListener, task::JoinSet}; 21 + use tower_http::{ 22 + decompression::RequestDecompressionLayer, request_id::PropagateRequestIdLayer, 23 + trace::TraceLayer, 24 + }; 25 + use tracing::{Span, field::Empty}; 26 + use url::Url; 27 + 28 + mod cli; 29 + 30 + fn main() { 31 + tracing_subscriber::fmt::init(); 32 + 33 + let arguments = cli::parse(); 34 + tracing::debug!(?arguments); 35 + 36 + Builder::new_multi_thread() 37 + .enable_all() 38 + .build() 39 + .expect("Failed to build runtime") 40 + .block_on(run(arguments)) 41 + .unwrap(); 42 + } 43 + 44 + const REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); 45 + 46 + #[derive(Deserialize)] 47 + struct Repo { 48 + repo: RepoId, 49 + } 50 + 51 + fn extract_request_id<B>(request: &Request<B>) -> Option<&str> { 52 + request 53 + .headers() 54 + .get(&REQUEST_ID) 55 + .and_then(|hv| std::str::from_utf8(hv.as_bytes()).ok()) 56 + } 57 + 58 + pub async fn run(arguments: cli::Arguments) -> anyhow::Result<()> { 59 + let resolver = Resolver::default(); 60 + let owner = arguments 61 + .owner 62 + .into_did(&resolver) 63 + .await 64 + .expect("Failed to resolve owner handle"); 65 + 66 + let mut router = Router::new() 67 + .without_v07_checks() 68 + .merge(knot::public::router()); 69 + 70 + if let Some(upstream_port) = arguments.upstream_port { 71 + let url = Url::parse(&format!("http://localhost:{upstream_port}")) 72 + .expect("localhost URL should be valid"); 73 + let client = Client::builder(TokioExecutor::new()).build(HttpConnector::new()); 74 + router = router.fallback(async move |mut req: axum::extract::Request| -> Result<axum::response::Response, StatusCode>{ 75 + // Re-write the request URI. 76 + let mut uri = url.clone(); 77 + uri.set_path(req.uri().path()); 78 + uri.set_query(req.uri().query()); 79 + *req.uri_mut() = Uri::try_from(uri.as_str()).unwrap(); 80 + 81 + tracing::info!(%uri, "forwarding request to upstream"); 82 + 83 + Ok(client 84 + .request(req) 85 + .await 86 + .map_err(|_| StatusCode::BAD_GATEWAY)? 87 + .into_response()) 88 + }); 89 + } 90 + 91 + let router = router 92 + .layer(RequestDecompressionLayer::new()) 93 + .layer(PropagateRequestIdLayer::x_request_id()) 94 + .layer( 95 + TraceLayer::new_for_http() 96 + .make_span_with(|request: &Request<_>| { 97 + let method = request.method(); 98 + let uri = request.uri(); 99 + let path = uri.path(); 100 + 101 + let span = 102 + tracing::info_span!("public", id = Empty, ?method, ?path, repo = Empty); 103 + 104 + if let Some(request_id) = extract_request_id(request) { 105 + span.record("id", request_id); 106 + } 107 + 108 + if let Ok(Query(Repo { repo })) = Query::try_from_uri(uri) { 109 + span.record("repo", format!("{}/{}", repo.owner(), repo.name())); 110 + } 111 + 112 + span 113 + }) 114 + .on_request(|_: &Request<_>, _: &Span| {}) 115 + .on_response(|response: &Response<_>, latency: Duration, _: &Span| { 116 + tracing::info!(?latency, status = ?response.status()); 117 + }), 118 + ) 119 + .with_state(Knot::new(owner, arguments.repos)); 120 + 121 + let mut sockets = Vec::with_capacity(arguments.addr.len()); 122 + for addr in &arguments.addr { 123 + for socket in addr.to_socket_addrs()? { 124 + sockets.push(socket); 125 + } 126 + } 127 + 128 + let mut service = JoinSet::new(); 129 + for socket in sockets { 130 + serve(&mut service, socket, router.clone()).await; 131 + } 132 + 133 + for task in service.join_all().await { 134 + if let Err(error) = task { 135 + tracing::error!(?error, "failed to join axum::serve task"); 136 + } 137 + } 138 + 139 + Ok(()) 140 + } 141 + 142 + pub async fn serve(set: &mut JoinSet<std::io::Result<()>>, socket: SocketAddr, router: Router) { 143 + let listener = TcpListener::bind(socket) 144 + .await 145 + .expect("Failed to bind socket"); 146 + 147 + let addr = listener 148 + .local_addr() 149 + .expect("Failed to acquire local socket address"); 150 + 151 + tracing::info!(?addr, "listening on socket"); 152 + set.spawn(async move { axum::serve(listener, router).await }); 153 + }
+508
crates/knot/src/model.rs
··· 1 + use crate::{ 2 + RepoId, 3 + convert::time_to_offsetdatetime, 4 + model::errors::{HeadDetached, PathNotFound, RefNotFound, RepoEmpty, RepoError, RepoNotFound}, 5 + public::xrpc::XrpcError, 6 + types::sh::tangled::repo::{ 7 + BlobEncoding, BlobParams, BlobResponse, Branch, BranchesParams, BranchesResponse, 8 + DefaultBranchResponse, Diff, DiffParams, DiffResponse, GetDefaultBranchParams, JsonBlob, 9 + LogParams, LogResponse, Readme, Reference, Tag, TagParams, TagsResponse, TreeEntry, 10 + TreeParams, TreeResponse, 11 + }, 12 + }; 13 + use gix::{ 14 + Commit, ObjectId, Repository, ThreadSafeRepository, bstr::ByteSlice as _, open::Options, 15 + }; 16 + use identity::Did; 17 + use rustc_hash::FxHashMap; 18 + use std::{ 19 + cmp::Reverse, 20 + path::{Path, PathBuf}, 21 + str::FromStr, 22 + sync::{Arc, Mutex}, 23 + }; 24 + 25 + const READMES: &[&[u8]] = &[ 26 + b"README.md", 27 + b"readme.md", 28 + b"README", 29 + b"readme", 30 + b"README.markdown", 31 + b"readme.markdown", 32 + b"README.txt", 33 + b"readme.txt", 34 + b"README.rst", 35 + b"readme.rst", 36 + b"README.org", 37 + b"readme.org", 38 + b"README.asciidoc", 39 + b"readme.asciidoc", 40 + b"index.rst", 41 + ]; 42 + 43 + pub mod errors; 44 + mod gitoxide; 45 + 46 + #[derive(Clone)] 47 + pub struct Knot { 48 + inner: Arc<KnotState>, 49 + } 50 + 51 + impl std::fmt::Debug for Knot { 52 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 + f.debug_struct("Knot").finish_non_exhaustive() 54 + } 55 + } 56 + 57 + struct KnotState { 58 + owner: Did, 59 + repository_base: PathBuf, 60 + repository_cache: Mutex<FxHashMap<RepoId, ThreadSafeRepository>>, 61 + } 62 + 63 + trait RepositoryManager { 64 + fn open(&self, repo: &RepoId) -> Result<Repository, XrpcError>; 65 + } 66 + 67 + impl RepositoryManager for Knot { 68 + fn open(&self, repo: &RepoId) -> Result<Repository, XrpcError> { 69 + let mut cache = self 70 + .inner 71 + .repository_cache 72 + .lock() 73 + .unwrap_or_else(|mut poison| { 74 + poison.get_mut().clear(); 75 + poison.into_inner() 76 + }); 77 + 78 + if let Some(repository) = cache.get(repo) { 79 + return Ok(repository.to_thread_local()); 80 + } 81 + 82 + let mut path = self.inner.repository_base.clone(); 83 + path.push(repo.owner()); 84 + path.push(repo.name()); 85 + 86 + let repository = Options::default() 87 + .strict_config(true) 88 + .open_path_as_is(true) 89 + .open(path) 90 + .map_err(RepoNotFound)?; 91 + 92 + let local = repository.to_thread_local(); 93 + assert!(local.is_bare()); 94 + 95 + match std::env::var("NO_REPO_CACHE").as_deref() { 96 + Ok("true") => Ok(repository.to_thread_local()), 97 + _ => { 98 + cache.insert(repo.clone(), repository.clone()); 99 + Ok(repository.to_thread_local()) 100 + } 101 + } 102 + } 103 + } 104 + 105 + impl Knot { 106 + pub fn new(owner: Did, repository_base: impl AsRef<Path>) -> Self { 107 + let inner = Arc::new(KnotState { 108 + owner, 109 + repository_base: repository_base.as_ref().to_owned(), 110 + repository_cache: Default::default(), 111 + }); 112 + 113 + Self { inner } 114 + } 115 + 116 + #[inline] 117 + pub fn owner(&self) -> &Did { 118 + &self.inner.owner 119 + } 120 + 121 + pub fn base_path(&self) -> &Path { 122 + &self.inner.repository_base 123 + } 124 + } 125 + 126 + impl Knot { 127 + fn resolve_rev<'repo>( 128 + repo: &'repo Repository, 129 + rev: Option<&str>, 130 + ) -> Result<Commit<'repo>, XrpcError> { 131 + let revision = if let Some(refspec) = rev { 132 + match ObjectId::from_str(refspec) { 133 + Ok(id) => repo.find_commit(id).map_err(RefNotFound)?, 134 + Err(_) => { 135 + // Assume the refspec is a branch or tag. 136 + let mut reference = repo.find_reference(refspec).map_err(RefNotFound)?; 137 + reference.peel_to_commit().map_err(RefNotFound)? 138 + } 139 + } 140 + } else { 141 + repo.head_commit().map_err(RefNotFound)? 142 + }; 143 + 144 + Ok(revision) 145 + } 146 + 147 + pub async fn get_default_branch( 148 + &self, 149 + params: &GetDefaultBranchParams, 150 + ) -> Result<DefaultBranchResponse, XrpcError> { 151 + tokio::task::block_in_place(|| { 152 + let repo = self.open(&params.repo)?; 153 + 154 + // Assume HEAD points the intended default branch. This *should* be true 155 + // for a bare repository. 156 + let mut head = repo.head()?; 157 + let name = head 158 + .referent_name() 159 + .ok_or(HeadDetached)? 160 + .shorten() 161 + .to_string(); 162 + 163 + let hash = head.id().map(|id| id.into()); 164 + let when = head 165 + .peel_to_commit_in_place() 166 + .ok() 167 + .and_then(|commit| { 168 + commit 169 + .committer() 170 + .ok() 171 + .and_then(|committer| committer.time().ok()) 172 + }) 173 + .and_then(|time| time_to_offsetdatetime(&time).ok()); 174 + 175 + Ok(DefaultBranchResponse { name, hash, when }) 176 + }) 177 + } 178 + 179 + pub async fn branches(&self, params: &BranchesParams) -> Result<BranchesResponse, XrpcError> { 180 + tokio::task::block_in_place(|| { 181 + let repo = self.open(&params.repo)?; 182 + 183 + // Assume HEAD points to the intended default branch. This *should* be 184 + // true for a bare repository. 185 + let head = repo.head()?; 186 + let default_name = head 187 + .referent_name() 188 + .ok_or(HeadDetached)? 189 + .shorten() 190 + .to_string(); 191 + 192 + let mut branches = Vec::new(); 193 + for branch in repo 194 + .references()? 195 + .local_branches()? 196 + .skip(params.cursor) 197 + .take(params.limit.into()) 198 + { 199 + let Ok(branch) = branch.inspect_err(|error| tracing::error!(?error)) else { 200 + continue; 201 + }; 202 + 203 + let name = branch.name().shorten().to_string(); 204 + let Some(id) = branch.try_id() else { 205 + tracing::warn!(?name, "branch unborn, skipping"); 206 + continue; 207 + }; 208 + 209 + let Ok(commit) = repo.find_commit(id) else { 210 + tracing::error!(?name, ?id, "failed to find commit for branch"); 211 + continue; 212 + }; 213 + 214 + let is_default = name == default_name; 215 + branches.push(Branch { 216 + reference: Reference { 217 + name, 218 + hash: commit.id.into(), 219 + }, 220 + commit: commit.try_into()?, 221 + is_default, 222 + }); 223 + } 224 + 225 + Ok(BranchesResponse { branches }) 226 + }) 227 + } 228 + 229 + pub async fn log(&self, params: &LogParams) -> Result<LogResponse, XrpcError> { 230 + tokio::task::block_in_place(|| { 231 + let repo = self.open(&params.repo)?; 232 + 233 + let commit_graph = repo.commit_graph_if_enabled().unwrap(); 234 + let total = match &commit_graph { 235 + Some(cg) => cg 236 + .num_commits() 237 + .try_into() 238 + .expect("You must be at least 32 bits tall to enjoy this ride"), 239 + None => { 240 + tracing::warn!(?repo, "no commit-graph, counting commits manually"); 241 + repo.rev_walk([repo.head_id().map_err(RepoEmpty)?]) 242 + .all() 243 + .map_err(RepoError)? 244 + .count() 245 + } 246 + }; 247 + 248 + let tip = Self::resolve_rev(&repo, params.rev.as_deref())?; 249 + 250 + let mut commits = Vec::new(); 251 + for commit in repo 252 + .rev_walk([tip.id()]) 253 + .with_commit_graph(commit_graph) 254 + .all() 255 + .map_err(RepoError)? 256 + .skip(params.cursor) 257 + .take(params.limit.into()) 258 + { 259 + match commit { 260 + Ok(commit) => { 261 + let commit = repo.find_commit(commit.id()).map_err(RepoError)?; 262 + commits.push(commit.try_into().map_err(RepoError)?); 263 + } 264 + Err(error) => { 265 + tracing::error!(?error); 266 + break; 267 + } 268 + } 269 + } 270 + 271 + Ok(LogResponse { 272 + commits, 273 + log: true, 274 + total, 275 + page: 1 + params.cursor / usize::from(params.limit), 276 + per_page: params.limit, 277 + }) 278 + }) 279 + } 280 + 281 + pub async fn tags(&self, params: &TagParams) -> Result<TagsResponse, XrpcError> { 282 + // @TODO Implement cursor. 283 + tokio::task::block_in_place(|| { 284 + let repo = self.open(&params.repo)?; 285 + 286 + let mut tags: Vec<_> = repo 287 + .references()? 288 + .tags()? 289 + .filter_map(|tag| { 290 + tag.inspect_err(|error| tracing::error!(?error)) 291 + .ok()? 292 + .try_into() 293 + .inspect_err(|error| tracing::error!(?error)) 294 + .ok() 295 + }) 296 + .collect(); 297 + 298 + tags.sort_by_key(|tag: &Tag| { 299 + Reverse( 300 + tag.annotation 301 + .as_ref() 302 + .map(|an| an.tagger.as_ref().map(|tagger| tagger.when)), 303 + ) 304 + }); 305 + 306 + tags.truncate(params.limit); 307 + 308 + Ok(TagsResponse { tags }) 309 + }) 310 + } 311 + 312 + pub async fn tree(&self, params: &TreeParams) -> Result<TreeResponse, XrpcError> { 313 + tokio::task::block_in_place(|| { 314 + let repo = self.open(&params.repo)?; 315 + let tip = Self::resolve_rev(&repo, params.rev.as_deref())?; 316 + let dotdot = params.path.clone().and_then(|mut path| { 317 + path.pop(); 318 + match path.as_os_str().is_empty() { 319 + true => None, 320 + false => Some(path), 321 + } 322 + }); 323 + 324 + let mut parent = None; 325 + let mut tree = tip.tree()?; 326 + if let Some(subpath) = &params.path { 327 + let entry = tree 328 + .lookup_entry_by_path(subpath)? 329 + .ok_or(PathNotFound(subpath.to_string_lossy()))?; 330 + 331 + if !entry.mode().is_tree() { 332 + return Ok(TreeResponse { 333 + files: vec![], 334 + dotdot, 335 + parent: params.path.clone(), 336 + rev: params.rev.as_deref().unwrap_or_default().to_string(), 337 + readme: None, 338 + }); 339 + } 340 + 341 + let subtree = repo.find_tree(entry.id()).unwrap(); 342 + tree = subtree; 343 + parent = Some(subpath.to_path_buf()); 344 + } 345 + 346 + let mut files: Vec<TreeEntry> = vec![]; 347 + let mut readme = None; 348 + for entry in tree.iter() { 349 + let Ok(entry) = entry else { 350 + continue; 351 + }; 352 + 353 + if READMES.contains(&entry.filename().as_bytes()) 354 + && entry.mode().is_blob() 355 + && readme.is_none() 356 + { 357 + let mut file = repo.find_blob(entry.id())?; 358 + if let Ok(contents) = String::from_utf8(file.take_data()) { 359 + readme.replace(Readme { 360 + contents, 361 + filename: entry.filename().to_string(), 362 + }); 363 + } 364 + } 365 + 366 + let Ok(tree_entry) = entry.try_into() else { 367 + continue; 368 + }; 369 + files.push(tree_entry); 370 + } 371 + 372 + let files: Vec<_> = tree 373 + .iter() 374 + .filter_map(|entry| { 375 + let entry = entry.ok()?; 376 + let file: TreeEntry = entry.try_into().ok()?; 377 + Some(file) 378 + }) 379 + .collect(); 380 + 381 + Ok(TreeResponse { 382 + files, 383 + dotdot, 384 + parent, 385 + rev: params.rev.as_deref().unwrap_or_default().to_string(), 386 + readme, 387 + }) 388 + }) 389 + } 390 + 391 + pub async fn blob(&self, params: &BlobParams) -> Result<BlobResponse, XrpcError> { 392 + tokio::task::block_in_place(|| { 393 + let repo = self.open(&params.repo)?; 394 + let tip = Self::resolve_rev(&repo, params.rev.as_deref())?; 395 + let entry = tip 396 + .tree()? 397 + .lookup_entry_by_path(&params.path)? 398 + .ok_or(PathNotFound(params.path.to_string_lossy()))?; 399 + 400 + if !(entry.mode().is_blob() || entry.mode().is_link()) { 401 + panic!("Not a blob: {:?}", params.path); 402 + } 403 + 404 + let mut blob = entry.object()?.into_blob(); 405 + let data = blob.take_data(); 406 + if params.raw { 407 + return Ok(BlobResponse::Raw(data)); 408 + } 409 + 410 + let size = data.len(); 411 + let (content, is_binary, encoding) = match String::from_utf8(data) { 412 + Ok(content) => (content, false, BlobEncoding::Utf8), 413 + Err(error) => ( 414 + data_encoding::BASE64.encode(&error.into_bytes()), 415 + true, 416 + BlobEncoding::Base64, 417 + ), 418 + }; 419 + 420 + Ok(BlobResponse::Json(JsonBlob { 421 + content, 422 + encoding, 423 + is_binary, 424 + mime_type: "".to_string(), 425 + path: params.path.to_owned(), 426 + rev: params.rev.as_deref().unwrap_or_default().to_owned(), 427 + size, 428 + })) 429 + }) 430 + } 431 + 432 + pub async fn diff(&self, params: &DiffParams) -> Result<DiffResponse, XrpcError> { 433 + { 434 + tokio::task::block_in_place(|| { 435 + let repo = self.open(&params.repo)?; 436 + 437 + let revision = match ObjectId::from_str(&params.rev) { 438 + Ok(id) => repo.find_commit(id).map_err(RefNotFound)?, 439 + Err(_) => { 440 + // Assume the refspec is a branch or tag. 441 + let mut reference = 442 + repo.find_reference(&params.rev).map_err(RefNotFound)?; 443 + reference.peel_to_commit().map_err(RefNotFound)? 444 + } 445 + }; 446 + 447 + // let parent_tree = match revision.parent_ids().next() { 448 + // Some(id) => repository.find_commit(id)?.tree()?, 449 + // None => repository.empty_tree(), 450 + // }; 451 + 452 + // let mut cache = repository 453 + // .diff_resource_cache( 454 + // gix::diff::blob::pipeline::Mode::ToGit, 455 + // WorktreeRoots::default(), 456 + // ) 457 + // .unwrap(); 458 + 459 + // let this_tree = revision.tree()?; 460 + // this_tree 461 + // .changes()? 462 + // .for_each_to_obtain_tree(&parent_tree, |change| { 463 + // tracing::debug!(?change); 464 + // if change.entry_mode().is_blob() { 465 + // println!("{}:", change.location().to_string()); 466 + // let mut line_diff = change.diff(&mut cache).unwrap(); 467 + // line_diff 468 + // .resource_cache 469 + // .options 470 + // .algorithm 471 + // .replace(Algorithm::Histogram); 472 + 473 + // let out = line_diff.lines(|_| Ok::<_, Er>(()) )?; 474 + 475 + // let input = out.interned_input(); 476 + // let diff = gix::diff::blob::diff( 477 + // Algorithm::Histogram, 478 + // &input, 479 + // UnifiedDiff::new( 480 + // &input, 481 + // String::new(), 482 + // gix::diff::blob::unified_diff::NewlineSeparator::AfterHeaderAndWhenNeeded("\n"), 483 + // ContextSize::symmetrical(3), 484 + // ), 485 + // ).unwrap() 486 + // .finish(); 487 + 488 + // println!("{diff}"); 489 + // } 490 + // Ok::<_, Er>(Action::Continue) 491 + // })?; 492 + 493 + let diff = Diff { 494 + commit: revision.try_into()?, 495 + stat: Default::default(), 496 + deltas: vec![], 497 + }; 498 + 499 + let response = DiffResponse { 500 + rev: params.rev.to_owned(), 501 + diff, 502 + }; 503 + 504 + Ok(response) 505 + }) 506 + } 507 + } 508 + }
+85
crates/knot/src/model/errors.rs
··· 1 + use crate::public::xrpc::XrpcError; 2 + use reqwest::StatusCode; 3 + use std::{borrow::Cow, fmt::Display}; 4 + 5 + pub struct InvalidRequest<E: Display>(pub E); 6 + 7 + impl<E: Display> From<InvalidRequest<E>> for XrpcError { 8 + fn from(InvalidRequest(error): InvalidRequest<E>) -> Self { 9 + Self { 10 + status: StatusCode::NOT_FOUND, 11 + error: Cow::Borrowed("InvalidRequest"), 12 + message: Cow::Owned(error.to_string()), 13 + } 14 + } 15 + } 16 + 17 + pub struct RepoNotFound<E: Display>(pub E); 18 + 19 + impl<E: Display> From<RepoNotFound<E>> for XrpcError { 20 + fn from(RepoNotFound(error): RepoNotFound<E>) -> Self { 21 + Self { 22 + status: StatusCode::NOT_FOUND, 23 + error: Cow::Borrowed("RepoNotFound"), 24 + message: Cow::Owned(error.to_string().replace('"', "'")), 25 + } 26 + } 27 + } 28 + 29 + pub struct RefNotFound<E: Display>(pub E); 30 + 31 + impl<E: Display> From<RefNotFound<E>> for XrpcError { 32 + fn from(RefNotFound(error): RefNotFound<E>) -> Self { 33 + Self { 34 + status: StatusCode::NOT_FOUND, 35 + error: Cow::Borrowed("RefNotFound"), 36 + message: Cow::Owned(error.to_string()), 37 + } 38 + } 39 + } 40 + 41 + pub struct RepoEmpty<E: Display>(pub E); 42 + 43 + impl<E: Display> From<RepoEmpty<E>> for XrpcError { 44 + fn from(value: RepoEmpty<E>) -> Self { 45 + Self { 46 + status: StatusCode::NOT_FOUND, 47 + error: Cow::Borrowed("RepoEmpty"), 48 + message: Cow::Owned(value.0.to_string()), 49 + } 50 + } 51 + } 52 + 53 + pub struct RepoError<E: Display>(pub E); 54 + 55 + impl<E: Display> From<RepoError<E>> for XrpcError { 56 + fn from(value: RepoError<E>) -> Self { 57 + Self { 58 + status: StatusCode::INTERNAL_SERVER_ERROR, 59 + error: Cow::Borrowed("RepoError"), 60 + message: Cow::Owned(value.0.to_string()), 61 + } 62 + } 63 + } 64 + 65 + #[derive(Debug, thiserror::Error)] 66 + #[error("Repository head is detached")] 67 + pub struct HeadDetached; 68 + 69 + impl From<HeadDetached> for XrpcError { 70 + fn from(value: HeadDetached) -> Self { 71 + (StatusCode::INTERNAL_SERVER_ERROR, value).into() 72 + } 73 + } 74 + 75 + pub struct PathNotFound<E: Display>(pub E); 76 + 77 + impl<E: Display> From<PathNotFound<E>> for XrpcError { 78 + fn from(value: PathNotFound<E>) -> Self { 79 + Self { 80 + status: StatusCode::NOT_FOUND, 81 + error: Cow::Borrowed("PathNotFound"), 82 + message: Cow::Owned(value.0.to_string()), 83 + } 84 + } 85 + }
+137
crates/knot/src/model/gitoxide.rs
··· 1 + use crate::{ 2 + public::xrpc::XrpcError, 3 + types::sh::tangled::repo::{Commit, Reference, Signature, Tag, TagAnnotation, TreeEntry}, 4 + }; 5 + use data_encoding::BASE64URL; 6 + use gix::bstr::ByteSlice; 7 + use std::{borrow::Cow, collections::HashMap}; 8 + 9 + impl TryFrom<gix::actor::SignatureRef<'_>> for Signature { 10 + type Error = XrpcError; 11 + 12 + fn try_from(value: gix::actor::SignatureRef<'_>) -> Result<Self, Self::Error> { 13 + let signature = value.trim(); 14 + Ok(Self { 15 + name: signature.name.to_string(), 16 + email: signature.email.to_string(), 17 + when: crate::convert::time_to_offsetdatetime(&signature.time()?)?, 18 + }) 19 + } 20 + } 21 + 22 + impl TryFrom<gix::Commit<'_>> for Commit { 23 + type Error = XrpcError; 24 + 25 + fn try_from(value: gix::Commit<'_>) -> Result<Self, Self::Error> { 26 + let id = value.id(); 27 + let decoded = value.decode()?; 28 + let signature = value.signature()?.map(|(sig, _)| sig.to_string()); 29 + let mut merge_tag = String::default(); 30 + let mut extra_headers = HashMap::default(); 31 + for (key, value) in decoded.extra_headers { 32 + match key.as_bytes() { 33 + b"mergetag" => merge_tag = value.to_string(), 34 + b"gpgsig" => {} 35 + _ => { 36 + extra_headers.insert(key.to_string(), BASE64URL.encode(value.as_bytes())); 37 + } 38 + } 39 + } 40 + 41 + Ok(Self { 42 + hash: id.into(), 43 + author: decoded.author.try_into()?, 44 + committer: decoded.committer.try_into()?, 45 + merge_tag, 46 + signature, 47 + message: decoded.message.to_string(), 48 + tree_hash: value.tree_id()?.into(), 49 + parent_hashes: value.parent_ids().map(|id| id.into()).collect(), 50 + encoding: Cow::Borrowed("UTF-8"), 51 + extra_headers, 52 + }) 53 + } 54 + } 55 + 56 + impl TryFrom<&gix::Reference<'_>> for Reference { 57 + type Error = anyhow::Error; 58 + 59 + fn try_from(value: &gix::Reference<'_>) -> Result<Self, Self::Error> { 60 + let name = value.name().shorten().to_string(); 61 + let hash = value.id().into(); 62 + 63 + Ok(Self { name, hash }) 64 + } 65 + } 66 + 67 + impl TryFrom<gix::Tag<'_>> for TagAnnotation { 68 + type Error = anyhow::Error; 69 + 70 + fn try_from(value: gix::Tag<'_>) -> Result<Self, Self::Error> { 71 + use gix::object::Kind; 72 + 73 + let hash = value.id.into(); 74 + let decoded = value.decode()?; 75 + 76 + // cf. <https://github.com/git/git/blob/7014b55638da979331baf8dc31c4e1d697cf2d67/object.h#L97> 77 + let target_type = match decoded.target_kind { 78 + Kind::Commit => 1, 79 + Kind::Tree => 2, 80 + Kind::Blob => 3, 81 + Kind::Tag => 4, 82 + }; 83 + 84 + Ok(Self { 85 + hash, 86 + name: decoded.name.to_string(), 87 + tagger: decoded.tagger.and_then(|tagger| tagger.try_into().ok()), 88 + message: decoded.message.to_string(), 89 + signature: decoded.pgp_signature.map(ToString::to_string), 90 + target_type, 91 + target: decoded.target().into(), 92 + }) 93 + } 94 + } 95 + 96 + impl TryFrom<gix::Reference<'_>> for Tag { 97 + type Error = anyhow::Error; 98 + 99 + fn try_from(mut value: gix::Reference<'_>) -> Result<Self, Self::Error> { 100 + let r#ref: Reference = (&value).try_into()?; 101 + 102 + Ok(Self { 103 + r#ref, 104 + annotation: value.peel_to_tag().ok().and_then(|tag| tag.try_into().ok()), 105 + }) 106 + } 107 + } 108 + 109 + impl TryFrom<gix::object::tree::EntryRef<'_, '_>> for TreeEntry { 110 + type Error = anyhow::Error; 111 + 112 + fn try_from(value: gix::object::tree::EntryRef<'_, '_>) -> Result<Self, Self::Error> { 113 + use gix::objs::tree::EntryKind; 114 + 115 + let name = value.filename().to_string(); 116 + let mode = value.mode(); 117 + 118 + let is_file = mode.is_blob(); 119 + let is_subtree = mode.is_tree(); 120 + 121 + let mode_string = match mode.kind() { 122 + EntryKind::Tree => "drwxr-xr-x", 123 + EntryKind::Blob => "-rw-r--r--", 124 + EntryKind::BlobExecutable => "-rwxr-xr-x", 125 + EntryKind::Link => "----------", 126 + EntryKind::Commit => "----------", 127 + }; 128 + 129 + Ok(Self { 130 + name, 131 + mode: Cow::Borrowed(mode_string), 132 + is_file, 133 + is_subtree, 134 + ..Default::default() 135 + }) 136 + } 137 + }
+127
crates/knot/src/objectid.rs
··· 1 + use serde::{Deserialize, Serialize, de::Visitor, ser::SerializeSeq}; 2 + use std::{marker::PhantomData, str::FromStr}; 3 + 4 + #[doc(hidden)] 5 + pub struct Hex; 6 + #[doc(hidden)] 7 + pub struct Array; 8 + 9 + #[derive(Clone, PartialEq, Eq)] 10 + pub struct ObjectId<E = Hex> { 11 + inner: gix::ObjectId, 12 + enc: PhantomData<E>, 13 + } 14 + 15 + impl<E> ObjectId<E> { 16 + pub fn id(&self) -> gix::ObjectId { 17 + self.inner 18 + } 19 + } 20 + 21 + impl<E> Default for ObjectId<E> { 22 + #[inline] 23 + fn default() -> Self { 24 + Self { 25 + inner: gix::ObjectId::null(gix::hash::Kind::Sha1), 26 + enc: PhantomData, 27 + } 28 + } 29 + } 30 + 31 + impl<E> std::fmt::Debug for ObjectId<E> { 32 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 + write!(f, "{}", self.inner) 34 + } 35 + } 36 + 37 + impl<E> From<gix::ObjectId> for ObjectId<E> { 38 + #[inline] 39 + fn from(value: gix::ObjectId) -> Self { 40 + Self { 41 + inner: value, 42 + enc: PhantomData, 43 + } 44 + } 45 + } 46 + 47 + impl<E> From<ObjectId<E>> for gix::ObjectId { 48 + #[inline] 49 + fn from(value: ObjectId<E>) -> Self { 50 + value.inner 51 + } 52 + } 53 + 54 + impl<E> From<gix::Id<'_>> for ObjectId<E> { 55 + #[inline] 56 + fn from(value: gix::Id<'_>) -> Self { 57 + Self { 58 + inner: value.detach(), 59 + enc: PhantomData, 60 + } 61 + } 62 + } 63 + 64 + const ENCODING: data_encoding::Encoding = data_encoding::HEXLOWER_PERMISSIVE; 65 + 66 + impl Serialize for ObjectId<Hex> { 67 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 68 + where 69 + S: serde::Serializer, 70 + { 71 + let s = ENCODING.encode(self.inner.as_bytes()); 72 + serializer.serialize_str(&s) 73 + } 74 + } 75 + 76 + impl Serialize for ObjectId<Array> { 77 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 78 + where 79 + S: serde::Serializer, 80 + { 81 + let bytes = self.inner.as_bytes(); 82 + let mut seq = serializer.serialize_seq(Some(bytes.len()))?; 83 + for element in bytes { 84 + seq.serialize_element(element)?; 85 + } 86 + seq.end() 87 + } 88 + } 89 + 90 + impl FromStr for ObjectId<Hex> { 91 + type Err = gix::hash::decode::Error; 92 + 93 + fn from_str(s: &str) -> Result<Self, Self::Err> { 94 + let id = gix::ObjectId::from_hex(s.as_bytes())?; 95 + Ok(Self { 96 + inner: id, 97 + enc: PhantomData, 98 + }) 99 + } 100 + } 101 + 102 + struct Hash; 103 + 104 + impl<'de> Visitor<'de> for Hash { 105 + type Value = ObjectId<Hex>; 106 + 107 + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 108 + formatter.write_str("commit hash") 109 + } 110 + 111 + fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 112 + where 113 + E: serde::de::Error, 114 + { 115 + let id = ObjectId::from_str(v).map_err(serde::de::Error::custom)?; 116 + Ok(id) 117 + } 118 + } 119 + 120 + impl<'de> Deserialize<'de> for ObjectId<Hex> { 121 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 122 + where 123 + D: serde::Deserializer<'de>, 124 + { 125 + deserializer.deserialize_any(Hash) 126 + } 127 + }
+70
crates/knot/src/public.rs
··· 1 + use crate::model::Knot; 2 + use axum::{ 3 + Router, 4 + extract::{WebSocketUpgrade, ws::WebSocket}, 5 + }; 6 + use std::time::Duration; 7 + use tokio::time::Instant; 8 + 9 + pub mod git; 10 + pub mod xrpc; 11 + 12 + pub fn router() -> Router<Knot> { 13 + Router::new() 14 + .without_v07_checks() 15 + .route("/events", axum::routing::get(events_handler)) 16 + .nest("/xrpc", xrpc::router()) 17 + } 18 + 19 + pub async fn events_handler(ws: WebSocketUpgrade) -> axum::response::Response { 20 + ws.on_upgrade(handle_events_socket) 21 + } 22 + 23 + pub async fn handle_events_socket(mut socket: WebSocket) { 24 + use axum::extract::ws::Message; 25 + 26 + let start = Instant::now(); 27 + let mut timeout = tokio::time::interval(Duration::from_secs(45)); 28 + 29 + // The first tick of an interval always resolves immediately. 30 + timeout.tick().await; 31 + 32 + tracing::trace!(?socket, "new websocket connection"); 33 + loop { 34 + let message = tokio::select! { 35 + now = timeout.tick() => { 36 + let diff = now.saturating_duration_since(start); 37 + let payload = format!("{}", diff.as_millis()); 38 + if let Err(error) = socket.send(Message::Ping(payload.into())).await { 39 + tracing::error!(?error); 40 + break; 41 + } 42 + continue; 43 + } 44 + Some(message) = socket.recv() => match message { 45 + Ok(message) => message, 46 + Err(error) => { 47 + tracing::error!(?error); 48 + break; 49 + } 50 + } 51 + }; 52 + 53 + match message { 54 + Message::Pong(payload) => match std::str::from_utf8(&payload) { 55 + Ok(payload) => tracing::debug!(%payload, "received pong"), 56 + Err(error) => { 57 + tracing::error!(?error, ?payload, "received non-utf8 payload in pong") 58 + } 59 + }, 60 + Message::Close(_) => { 61 + tracing::debug!("client closed connection"); 62 + break; 63 + } 64 + Message::Text(payload) => { 65 + tracing::info!(payload = payload.as_str(), "received message from client"); 66 + } 67 + _ => {} // 68 + } 69 + } 70 + }
crates/knot/src/public/git.rs
+139
crates/knot/src/public/xrpc.rs
··· 1 + use crate::model::Knot; 2 + use axum::{Json, Router, extract::FromRef, http::StatusCode, response::IntoResponse}; 3 + use std::borrow::Cow; 4 + 5 + pub mod sh; 6 + 7 + pub fn router<S: Clone + Send + Sync + 'static>() -> Router<S> 8 + where 9 + Knot: FromRef<S>, 10 + { 11 + Router::<S>::new() 12 + .without_v07_checks() 13 + .merge(sh::tangled::owner()) 14 + .merge(sh::tangled::knot::version()) 15 + .merge(sh::tangled::repo::blob()) 16 + .merge(sh::tangled::repo::branches()) 17 + // .merge(sh::tangled::repo::diff()) 18 + .merge(sh::tangled::repo::get_default_branch()) 19 + .merge(sh::tangled::repo::languages()) 20 + .merge(sh::tangled::repo::log()) 21 + .merge(sh::tangled::repo::tags()) 22 + .merge(sh::tangled::repo::tree()) 23 + } 24 + 25 + #[derive(Debug, Default)] 26 + pub struct XrpcError { 27 + pub status: StatusCode, 28 + pub error: Cow<'static, str>, 29 + pub message: Cow<'static, str>, 30 + } 31 + 32 + impl XrpcError { 33 + pub fn new( 34 + status: impl Into<StatusCode>, 35 + error: &'static str, 36 + message: impl Into<Cow<'static, str>>, 37 + ) -> Self { 38 + Self { 39 + status: status.into(), 40 + error: Cow::Borrowed(error), 41 + message: message.into(), 42 + } 43 + } 44 + 45 + pub fn from_static( 46 + status: impl Into<StatusCode>, 47 + error: &'static str, 48 + message: &'static str, 49 + ) -> Self { 50 + Self { 51 + status: status.into(), 52 + error: Cow::Borrowed(error), 53 + message: Cow::Borrowed(message), 54 + } 55 + } 56 + } 57 + 58 + impl std::fmt::Display for XrpcError { 59 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 + write!(f, "{self:?}") 61 + } 62 + } 63 + 64 + impl IntoResponse for XrpcError { 65 + fn into_response(self) -> axum::response::Response { 66 + #[derive(serde::Serialize)] 67 + struct Body<'a> { 68 + error: &'a str, 69 + message: &'a str, 70 + } 71 + 72 + let body = Body { 73 + error: &self.error, 74 + message: &self.message, 75 + }; 76 + 77 + (self.status, Json(body)).into_response() 78 + } 79 + } 80 + 81 + impl<T: std::fmt::Display + std::fmt::Debug> From<(StatusCode, T)> for XrpcError { 82 + fn from((status, error): (StatusCode, T)) -> Self { 83 + Self { 84 + status, 85 + error: Cow::Owned(format!("{error:?}")), 86 + message: Cow::Owned(format!("{error}")), 87 + } 88 + } 89 + } 90 + 91 + macro_rules! ise { 92 + ($t:ty, $status:expr, $typ:literal) => { 93 + impl From<$t> for XrpcError { 94 + fn from(value: $t) -> Self { 95 + Self { 96 + status: $status, 97 + error: Cow::Borrowed($typ), 98 + message: Cow::Owned(format!("{value}").replace('"', "\'")), 99 + } 100 + } 101 + } 102 + }; 103 + ($t:ty, $status:expr) => { 104 + impl From<$t> for XrpcError { 105 + fn from(value: $t) -> Self { 106 + ($status, value).into() 107 + } 108 + } 109 + }; 110 + ($t:ty) => { 111 + ise!($t, StatusCode::INTERNAL_SERVER_ERROR); 112 + }; 113 + } 114 + 115 + ise!(std::io::Error); 116 + ise!(gix::date::parse::Error); 117 + ise!(gix::diff::blob::platform::set_resource::Error); 118 + ise!(gix::diff::options::init::Error); 119 + ise!(gix::objs::decode::Error); 120 + ise!(gix::object::commit::Error); 121 + ise!(gix::object::find::existing::Error); 122 + ise!(gix::object::find::existing::with_conversion::Error); 123 + ise!(gix::object::tree::diff::for_each::Error); 124 + ise!(gix::reference::iter::Error); 125 + ise!(gix::reference::iter::init::Error); 126 + ise!(gix::reference::head_tree::Error); 127 + ise!(gix::reference::find::existing::Error, StatusCode::NOT_FOUND); 128 + 129 + // A `ComponentRange` error can occur when constructing an `OffsetDateTime` 130 + // from a git timestamp. 131 + impl From<time::error::ComponentRange> for XrpcError { 132 + fn from(value: time::error::ComponentRange) -> Self { 133 + Self { 134 + status: StatusCode::INTERNAL_SERVER_ERROR, 135 + error: Cow::Borrowed("TimestampConversionError"), 136 + message: Cow::Owned(value.to_string()), 137 + } 138 + } 139 + }
+1
crates/knot/src/public/xrpc/sh.rs
··· 1 + pub mod tangled;
+17
crates/knot/src/public/xrpc/sh/tangled.rs
··· 1 + use crate::{model::Knot, types::sh::tangled::Owner}; 2 + use axum::{ 3 + Json, 4 + extract::{FromRef, State}, 5 + }; 6 + 7 + pub mod knot; 8 + pub mod repo; 9 + 10 + #[xrpc::query("sh.tangled.owner")] 11 + #[tracing::instrument] 12 + pub async fn owner(State(knot): State<Knot>) -> Json<Owner> 13 + where 14 + Knot: FromRef<S>, 15 + { 16 + Owner::new(knot.owner().clone()).into() 17 + }
+14
crates/knot/src/public/xrpc/sh/tangled/knot.rs
··· 1 + use axum::Json; 2 + 3 + /// Response type for 'sh.tangled.knot.version'. 4 + #[derive(Debug, serde::Serialize)] 5 + pub struct Version { 6 + pub version: &'static str, 7 + } 8 + 9 + #[xrpc::query("sh.tangled.knot.version")] 10 + pub async fn version() -> Json<Version> { 11 + Json(Version { 12 + version: concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")), 13 + }) 14 + }
+115
crates/knot/src/public/xrpc/sh/tangled/repo.rs
··· 1 + use crate::{ 2 + model::{Knot, errors::InvalidRequest}, 3 + public::xrpc::XrpcError, 4 + types::sh::tangled::repo::*, 5 + }; 6 + use axum::{ 7 + Json, 8 + extract::{FromRef, Query, State}, 9 + response::{IntoResponse, Response}, 10 + }; 11 + 12 + type Result<T> = std::result::Result<Json<T>, XrpcError>; 13 + 14 + #[xrpc::query("sh.tangled.repo.getDefaultBranch")] 15 + #[tracing::instrument] 16 + pub async fn get_default_branch( 17 + State(knot): State<Knot>, 18 + Query(params): Query<GetDefaultBranchParams>, 19 + ) -> Result<DefaultBranchResponse> 20 + where 21 + Knot: FromRef<S>, 22 + { 23 + Ok(knot.get_default_branch(&params).await?.into()) 24 + } 25 + 26 + #[xrpc::query("sh.tangled.repo.branches")] 27 + #[tracing::instrument] 28 + pub async fn branches( 29 + State(knot): State<Knot>, 30 + Query(params): Query<BranchesParams>, 31 + ) -> Result<BranchesResponse> 32 + where 33 + Knot: FromRef<S>, 34 + { 35 + const BRANCHES_LIMIT_MIN: u16 = 1; 36 + const BRANCHES_LIMIT_MAX: u16 = 100; 37 + if !(BRANCHES_LIMIT_MIN..=BRANCHES_LIMIT_MAX).contains(&params.limit) { 38 + return Err(InvalidRequest(format!( 39 + "limit outside acceptable range ({BRANCHES_LIMIT_MIN}..={BRANCHES_LIMIT_MAX})" 40 + )))?; 41 + } 42 + 43 + Ok(knot.branches(&params).await?.into()) 44 + } 45 + 46 + #[xrpc::query("sh.tangled.repo.log")] 47 + #[tracing::instrument] 48 + pub async fn log(State(knot): State<Knot>, Query(params): Query<LogParams>) -> Result<LogResponse> 49 + where 50 + Knot: FromRef<S>, 51 + { 52 + const LOG_LIMIT_MIN: u16 = 1; 53 + const LOG_LIMIT_MAX: u16 = 100; 54 + if !(LOG_LIMIT_MIN..=LOG_LIMIT_MAX).contains(&params.limit) { 55 + return Err(InvalidRequest(format!( 56 + "limit outside acceptable range ({LOG_LIMIT_MIN}..={LOG_LIMIT_MAX})" 57 + )))?; 58 + } 59 + 60 + Ok(knot.log(&params).await?.into()) 61 + } 62 + 63 + #[xrpc::query("sh.tangled.repo.tags")] 64 + #[tracing::instrument] 65 + pub async fn tags(State(knot): State<Knot>, Query(params): Query<TagParams>) -> Result<TagsResponse> 66 + where 67 + Knot: FromRef<S>, 68 + { 69 + Ok(knot.tags(&params).await?.into()) 70 + } 71 + 72 + #[xrpc::query("sh.tangled.repo.tree")] 73 + #[tracing::instrument] 74 + pub async fn tree( 75 + State(knot): State<Knot>, 76 + Query(params): Query<TreeParams>, 77 + ) -> Result<TreeResponse> 78 + where 79 + Knot: FromRef<S>, 80 + { 81 + Ok(knot.tree(&params).await?.into()) 82 + } 83 + 84 + #[xrpc::query("sh.tangled.repo.blob")] 85 + #[tracing::instrument] 86 + pub async fn blob( 87 + State(knot): State<Knot>, 88 + Query(params): Query<BlobParams>, 89 + ) -> std::result::Result<Response, XrpcError> 90 + where 91 + Knot: FromRef<S>, 92 + { 93 + match knot.blob(&params).await? { 94 + BlobResponse::Json(blob) => Ok(Json(blob).into_response()), 95 + BlobResponse::Raw(blob) => Ok(blob.into_response()), 96 + } 97 + } 98 + 99 + #[xrpc::query("sh.tangled.repo.languages")] 100 + #[tracing::instrument] 101 + pub async fn languages() -> Json<Languages> { 102 + Default::default() 103 + } 104 + 105 + #[xrpc::query("sh.tangled.repo.diff")] 106 + #[tracing::instrument] 107 + pub async fn diff( 108 + State(knot): State<Knot>, 109 + Query(params): Query<DiffParams>, 110 + ) -> Result<DiffResponse> 111 + where 112 + Knot: FromRef<S>, 113 + { 114 + Ok(knot.diff(&params).await?.into()) 115 + }
+63
crates/knot/src/repoid.rs
··· 1 + use identity::Did; 2 + use serde::{ 3 + Deserialize, Deserializer, 4 + de::{Error, Visitor}, 5 + }; 6 + 7 + #[derive(Clone, Debug, PartialEq, Eq, Hash)] 8 + pub struct RepoId { 9 + /// DID of the repository owner 10 + pub owner: Did, 11 + 12 + /// Repository name 13 + pub name: Box<str>, 14 + } 15 + 16 + impl RepoId { 17 + #[inline] 18 + pub const fn owner(&self) -> &str { 19 + self.owner.as_str() 20 + } 21 + 22 + #[inline] 23 + pub const fn name(&self) -> &str { 24 + &self.name 25 + } 26 + } 27 + 28 + impl<'de> Deserialize<'de> for RepoId { 29 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 30 + where 31 + D: Deserializer<'de>, 32 + { 33 + struct RepoIdVisitor; 34 + 35 + impl<'de> Visitor<'de> for RepoIdVisitor { 36 + type Value = RepoId; 37 + 38 + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 39 + formatter.write_str("repository identifier in the form '{owner}/{name}'") 40 + } 41 + 42 + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 43 + where 44 + E: Error, 45 + { 46 + let Some((owner, name)) = v.split_once('/') else { 47 + return Err(Error::custom( 48 + "Expected a repository identifier in the form '{owner}/{name}'", 49 + )); 50 + }; 51 + 52 + let owner = owner.parse().map_err(Error::custom)?; 53 + 54 + Ok(Self::Value { 55 + owner, 56 + name: name.into(), 57 + }) 58 + } 59 + } 60 + 61 + deserializer.deserialize_str(RepoIdVisitor) 62 + } 63 + }
+23
crates/knot/src/revspec.rs
··· 1 + use axum::{ 2 + extract::{FromRequestParts, Query, rejection::QueryRejection}, 3 + http::request::Parts, 4 + }; 5 + use serde::Deserialize; 6 + 7 + #[derive(Debug, Deserialize)] 8 + pub struct RevSpec { 9 + pub r#ref: String, 10 + } 11 + 12 + impl<S: Send + Sync> FromRequestParts<S> for RevSpec { 13 + type Rejection = QueryRejection; 14 + 15 + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 16 + let Query(revspec) = Query::from_request_parts(parts, state).await?; 17 + Ok(revspec) 18 + } 19 + } 20 + 21 + impl RevSpec { 22 + // 23 + }
+358
crates/knot/src/types.rs
··· 1 + pub mod sh { 2 + pub mod tangled { 3 + use identity::Did; 4 + use serde::{Deserialize, Serialize}; 5 + use time::OffsetDateTime; 6 + 7 + /// Response for `sh.tangled.owner`. 8 + #[derive(Debug, Serialize)] 9 + pub struct Owner { 10 + pub owner: Did, 11 + } 12 + 13 + impl Owner { 14 + pub fn new(owner: Did) -> Self { 15 + Self { owner } 16 + } 17 + } 18 + 19 + /// Lexicon definition `sh.tangled.repo` 20 + #[derive(Debug, Deserialize)] 21 + #[serde(rename_all = "camelCase")] 22 + pub struct Repo { 23 + #[serde(rename = "$type")] 24 + pub typ: String, 25 + pub knot: String, 26 + pub name: String, 27 + #[serde(default)] 28 + pub labels: Vec<String>, 29 + #[serde(with = "time::serde::rfc3339")] 30 + pub created_at: OffsetDateTime, 31 + } 32 + 33 + pub mod repo { 34 + use crate::{ObjectId, RepoId, objectid::Array}; 35 + use serde::{Deserialize, Serialize}; 36 + use std::{borrow::Cow, collections::HashMap, path::PathBuf}; 37 + use time::OffsetDateTime; 38 + 39 + #[derive(Debug, Deserialize)] 40 + pub struct GetDefaultBranchParams { 41 + pub repo: RepoId, 42 + } 43 + 44 + /// Response type for the `sh.tangled.repo.getDefaultBranch` query. 45 + #[derive(Debug, Default, Serialize)] 46 + #[serde(rename_all = "camelCase")] 47 + pub struct DefaultBranchResponse { 48 + /// Short-name of the default branch. 49 + pub name: String, 50 + 51 + /// ID of the most recent commit on the default branch 52 + // 53 + // @NOTE Official knotserver always returns an empty string. 54 + #[serde(skip_serializing_if = "Option::is_none")] 55 + pub hash: Option<ObjectId>, 56 + 57 + /// Timestamp of the most recent commit on the default branch 58 + // 59 + // @NOTE Official knotserver always returns the unix epoch. 60 + #[serde( 61 + with = "time::serde::rfc3339::option", 62 + skip_serializing_if = "Option::is_none" 63 + )] 64 + pub when: Option<OffsetDateTime>, 65 + } 66 + 67 + #[derive(Debug, Default, Serialize)] 68 + #[serde(rename_all = "camelCase")] 69 + pub struct Reference { 70 + /// Short-name of the reference. 71 + pub name: String, 72 + 73 + // @TODO What is this for? 74 + pub hash: ObjectId, 75 + } 76 + 77 + /// Git commit signature (ie. the author or committer). 78 + #[derive(Debug, Serialize)] 79 + #[serde(rename_all = "PascalCase")] 80 + pub struct Signature { 81 + pub name: String, 82 + pub email: String, 83 + #[serde(with = "time::serde::rfc3339")] 84 + pub when: OffsetDateTime, 85 + } 86 + 87 + #[derive(Debug, Serialize)] 88 + #[serde(rename_all = "PascalCase")] 89 + pub struct Commit { 90 + pub hash: ObjectId<Array>, 91 + pub author: Signature, 92 + pub committer: Signature, 93 + pub merge_tag: String, 94 + #[serde(rename = "PGPSignature")] 95 + pub signature: Option<String>, 96 + pub message: String, 97 + pub tree_hash: ObjectId<Array>, 98 + pub parent_hashes: Vec<ObjectId<Array>>, 99 + pub encoding: Cow<'static, str>, 100 + /// Non-standard object headers. Values are always base64 encoded. 101 + pub extra_headers: HashMap<String, String>, 102 + } 103 + 104 + #[derive(Debug, Serialize)] 105 + #[serde(rename_all = "camelCase")] 106 + pub struct Branch { 107 + pub reference: Reference, 108 + pub commit: Commit, 109 + #[serde(rename = "is_deafult")] 110 + pub is_default: bool, 111 + } 112 + 113 + #[derive(Debug, Deserialize)] 114 + pub struct BranchesParams { 115 + pub repo: RepoId, 116 + #[serde(default = "branches_limit_default")] 117 + pub limit: u16, 118 + #[serde(default)] 119 + pub cursor: usize, 120 + } 121 + 122 + const fn branches_limit_default() -> u16 { 123 + 50 124 + } 125 + 126 + #[derive(Debug, Default, Serialize)] 127 + #[serde(rename_all = "camelCase")] 128 + pub struct BranchesResponse { 129 + #[serde(skip_serializing_if = "Vec::is_empty")] 130 + pub branches: Vec<Branch>, 131 + } 132 + 133 + #[derive(Debug, serde::Serialize)] 134 + pub struct LogResponse { 135 + pub commits: Vec<Commit>, 136 + pub log: bool, 137 + pub total: usize, 138 + pub page: usize, 139 + pub per_page: u16, 140 + } 141 + 142 + #[derive(Debug, Deserialize)] 143 + pub struct LogParams { 144 + pub repo: RepoId, 145 + #[serde(rename = "ref")] 146 + pub rev: Option<String>, 147 + // @NOTE The lexicon states `cursor` is a string containing a pagination 148 + // cursor of a commit id. AppView actually gives us an integer for 149 + // the number of commits to skip. 150 + #[serde(default)] 151 + pub cursor: usize, 152 + #[serde(default = "log_limit_default")] 153 + pub limit: u16, 154 + } 155 + 156 + const fn log_limit_default() -> u16 { 157 + 50 158 + } 159 + 160 + #[derive(Debug, Deserialize)] 161 + pub struct TagParams { 162 + pub repo: RepoId, 163 + #[serde(default)] 164 + pub cursor: usize, 165 + #[serde(default = "tag_limit_default")] 166 + pub limit: usize, 167 + } 168 + 169 + const fn tag_limit_default() -> usize { 170 + 50 171 + } 172 + 173 + #[derive(Debug, Serialize)] 174 + #[serde(rename_all = "PascalCase")] 175 + pub struct TagAnnotation { 176 + pub hash: ObjectId<Array>, 177 + pub name: String, 178 + pub tagger: Option<Signature>, 179 + pub message: String, 180 + #[serde(rename = "PGPSignature")] 181 + pub signature: Option<String>, 182 + pub target_type: i32, 183 + pub target: ObjectId<Array>, 184 + } 185 + 186 + #[derive(Debug, Serialize)] 187 + pub struct Tag { 188 + #[serde(flatten)] 189 + pub r#ref: Reference, 190 + #[serde(rename = "tag", skip_serializing_if = "Option::is_none")] 191 + pub annotation: Option<TagAnnotation>, 192 + } 193 + 194 + #[derive(Debug, Serialize)] 195 + pub struct TagsResponse { 196 + pub tags: Vec<Tag>, 197 + } 198 + 199 + #[derive(Debug, Default, Serialize)] 200 + pub struct TreeEntry { 201 + pub name: String, 202 + pub mode: Cow<'static, str>, 203 + pub size: usize, 204 + pub is_file: bool, 205 + pub is_subtree: bool, 206 + } 207 + 208 + #[derive(Debug, Deserialize)] 209 + pub struct TreeParams { 210 + pub repo: RepoId, 211 + #[serde(rename = "ref")] 212 + pub rev: Option<String>, 213 + pub path: Option<PathBuf>, 214 + } 215 + 216 + #[derive(Debug, Serialize)] 217 + pub struct Readme { 218 + pub contents: String, 219 + pub filename: String, 220 + } 221 + 222 + /// Return type for `sh.tangled.repo.tree`. 223 + #[derive(Debug, Serialize)] 224 + pub struct TreeResponse { 225 + pub files: Vec<TreeEntry>, 226 + #[serde(skip_serializing_if = "Option::is_none")] 227 + pub dotdot: Option<PathBuf>, 228 + #[serde(skip_serializing_if = "Option::is_none")] 229 + pub parent: Option<PathBuf>, 230 + #[serde(rename = "ref")] 231 + pub rev: String, 232 + #[serde(skip_serializing_if = "Option::is_none")] 233 + pub readme: Option<Readme>, 234 + } 235 + 236 + #[derive(Debug, Deserialize)] 237 + pub struct BlobParams { 238 + pub repo: RepoId, 239 + #[serde(rename = "ref")] 240 + pub rev: Option<String>, 241 + pub path: PathBuf, 242 + #[serde(default)] 243 + pub raw: bool, 244 + } 245 + 246 + #[derive(Debug, Serialize)] 247 + pub enum BlobEncoding { 248 + #[serde(rename = "utf-8")] 249 + Utf8, 250 + #[serde(rename = "base64")] 251 + Base64, 252 + } 253 + 254 + #[derive(Debug, Serialize)] 255 + #[serde(rename_all = "camelCase")] 256 + pub struct JsonBlob { 257 + pub content: String, 258 + pub encoding: BlobEncoding, 259 + pub is_binary: bool, 260 + pub mime_type: String, 261 + pub path: PathBuf, 262 + #[serde(rename = "ref")] 263 + pub rev: String, 264 + pub size: usize, 265 + } 266 + 267 + #[derive(Debug)] 268 + pub enum BlobResponse { 269 + Json(JsonBlob), 270 + Raw(Vec<u8>), 271 + } 272 + 273 + #[derive(Debug, Default, Serialize)] 274 + #[serde(rename_all = "PascalCase")] 275 + pub struct DiffLine { 276 + pub op: u8, 277 + pub line: String, 278 + } 279 + 280 + #[derive(Debug, Default, Serialize)] 281 + #[serde(rename_all = "PascalCase")] 282 + pub struct DiffHunk { 283 + // comment: String, 284 + pub old_position: u32, 285 + pub old_lines: u32, 286 + pub new_position: u32, 287 + pub new_lines: u32, 288 + pub lines_added: u32, 289 + pub lines_deleted: u32, 290 + pub leading_context: u32, 291 + pub trailing_context: u32, 292 + pub lines: Vec<DiffLine>, 293 + } 294 + 295 + #[derive(Debug, Default, Serialize)] 296 + pub struct Delta { 297 + pub name: DeltaName, 298 + #[serde(rename = "text_fragments")] 299 + pub hunks: Vec<DiffHunk>, 300 + pub is_binary: bool, 301 + pub is_new: bool, 302 + pub is_delete: bool, 303 + pub is_copy: bool, 304 + pub is_rename: bool, 305 + } 306 + 307 + #[derive(Debug, Default, Serialize)] 308 + pub struct DiffStat { 309 + pub files_changed: usize, 310 + pub insertions: usize, 311 + pub deletions: usize, 312 + } 313 + 314 + #[derive(Debug, Default, Serialize)] 315 + pub struct DeltaName { 316 + pub old: String, 317 + pub new: String, 318 + } 319 + 320 + #[derive(Debug, Serialize)] 321 + pub struct Diff { 322 + pub commit: Commit, 323 + pub stat: DiffStat, 324 + #[serde(rename = "diff")] 325 + pub deltas: Vec<Delta>, 326 + } 327 + 328 + #[derive(Debug, Deserialize)] 329 + pub struct DiffParams { 330 + pub repo: RepoId, 331 + #[serde(rename = "ref")] 332 + pub rev: String, 333 + } 334 + 335 + #[derive(Debug, Serialize)] 336 + pub struct DiffResponse { 337 + #[serde(rename = "ref")] 338 + pub rev: String, 339 + pub diff: Diff, 340 + } 341 + 342 + #[derive(Debug, Default, Serialize)] 343 + #[serde(rename_all = "camelCase")] 344 + pub struct Languages { 345 + #[serde(skip_serializing_if = "Vec::is_empty")] 346 + pub languages: Vec<()>, 347 + } 348 + 349 + // Mutations 350 + 351 + #[derive(Debug, Deserialize)] 352 + #[serde(rename_all = "camelCase")] 353 + pub struct CreateParams { 354 + pub rkey: String, 355 + } 356 + } 357 + } 358 + }
+20
crates/service_auth/Cargo.toml
··· 1 + [package] 2 + name = "service_auth" 3 + version.workspace = true 4 + edition.workspace = true 5 + authors.workspace = true 6 + repository.workspace = true 7 + license.workspace = true 8 + publish.workspace = true 9 + 10 + [dependencies] 11 + aws-lc-rs = { version = "1.14.1", default-features = false, features = ["alloc", "aws-lc-sys"] } 12 + axum.workspace = true 13 + data-encoding = "2.9.0" 14 + identity.workspace = true 15 + multibase = "0.9.1" 16 + serde = { workspace = true, features = ["derive"] } 17 + serde_json = { version = "1.0.145" } 18 + thiserror = "2.0.16" 19 + time = "0.3.44" 20 + tracing.workspace = true
+186
crates/service_auth/src/jwt.rs
··· 1 + use crate::key::PublicKey; 2 + use aws_lc_rs::signature::{self, UnparsedPublicKey}; 3 + use data_encoding::BASE64URL_NOPAD as Encoding; 4 + use identity::Did; 5 + use serde::{Deserialize, de::DeserializeOwned}; 6 + 7 + #[derive(Debug, Deserialize, PartialEq, Eq)] 8 + pub enum Type { 9 + JWT, 10 + } 11 + 12 + /// Signature algorithm. 13 + /// 14 + /// See: <https://atproto.com/specs/xrpc#inter-service-authentication-jwt> 15 + #[derive(Debug, Deserialize, PartialEq, Eq)] 16 + pub enum Algorithm { 17 + /// Secp256k1 18 + ES256K, 19 + /// Secp256r1 (aka prime256v1) 20 + ES256, 21 + } 22 + #[derive(Debug, Deserialize)] 23 + pub struct Header { 24 + pub typ: Type, 25 + 26 + /// Signing-key algorithm. 27 + pub alg: Algorithm, 28 + } 29 + 30 + /// Claims for inter-service authentication (JWT). 31 + /// 32 + /// See: <https://atproto.com/specs/xrpc#inter-service-authentication-jwt> 33 + #[derive(Debug, Deserialize)] 34 + pub struct Claims { 35 + /// Account DID associated with the service that the request is being 36 + /// sent to. 37 + pub iss: Did, 38 + 39 + /// Service DID associated with the service that the request is being 40 + /// sent to. 41 + pub aud: Did, 42 + 43 + /// Token expiration time as a UNIX timestamp. 44 + pub exp: i64, 45 + 46 + /// Token creation time as a UNIX timestamp. 47 + pub iat: i64, 48 + 49 + /// Lexicon method in NSID syntax. 50 + pub lxm: Option<String>, 51 + 52 + /// Nonce. 53 + pub jti: Box<String>, 54 + } 55 + 56 + #[derive(Debug, Deserialize)] 57 + pub struct Token<C> { 58 + pub header: Header, 59 + pub claims: C, 60 + } 61 + 62 + impl Token<Claims> { 63 + #[inline] 64 + pub fn decode(token: impl AsRef<[u8]>, key: &PublicKey) -> Result<Self, Error> { 65 + decode(token, key) 66 + } 67 + 68 + #[inline] 69 + pub fn decode_unverified(token: impl AsRef<[u8]>) -> Result<Self, Error> { 70 + decode_unverified(token) 71 + } 72 + } 73 + 74 + #[derive(Debug, thiserror::Error)] 75 + pub enum Error { 76 + #[error("Invalid token format")] 77 + InvalidFormat, 78 + #[error("Invalid token encoding: {0}")] 79 + InvalidEncoding(#[from] data_encoding::DecodeError), 80 + #[error("Failed to parse token: {0}")] 81 + InvalidStructure(#[from] serde_json::Error), 82 + #[error("Signature failure")] 83 + SignatureFailed, 84 + } 85 + 86 + type TokenParts<'a> = (&'a [u8], &'a [u8], &'a [u8]); 87 + 88 + fn split_token(bytes: &[u8]) -> Result<TokenParts<'_>, Error> { 89 + let mut parts = bytes.split(|bytes| bytes == &b'.'); 90 + match (parts.next(), parts.next(), parts.next(), parts.next()) { 91 + (Some(header), Some(claims), Some(signature), None) => Ok((header, claims, signature)), 92 + _ => Err(Error::InvalidFormat), 93 + } 94 + } 95 + 96 + fn parse<T: DeserializeOwned>(encoded_bytes: &[u8]) -> Result<T, Error> { 97 + let bytes = Encoding.decode(encoded_bytes)?; 98 + let data = serde_json::from_slice(&bytes)?; 99 + Ok(data) 100 + } 101 + 102 + pub fn decode_unverified<C: DeserializeOwned>(token: impl AsRef<[u8]>) -> Result<Token<C>, Error> { 103 + let (header, claims, _) = split_token(token.as_ref())?; 104 + 105 + let header = parse(header)?; 106 + let claims = parse(claims)?; 107 + 108 + Ok(Token { header, claims }) 109 + } 110 + 111 + pub fn decode<C: DeserializeOwned>( 112 + token: impl AsRef<[u8]>, 113 + key: &PublicKey, 114 + ) -> Result<Token<C>, Error> { 115 + let token = token.as_ref(); 116 + let (header, claims, signature) = split_token(token)?; 117 + 118 + let message = &token[..=header.len() + claims.len()]; 119 + let signature = Encoding.decode(signature)?; 120 + 121 + let header: Header = parse(header)?; 122 + match (&header.alg, &key) { 123 + (Algorithm::ES256K, PublicKey::K256(key)) => { 124 + let pk = UnparsedPublicKey::new(&signature::ECDSA_P256K1_SHA256_FIXED, key); 125 + pk.verify(message, &signature) 126 + .map_err(|_| Error::SignatureFailed)?; 127 + } 128 + (Algorithm::ES256, PublicKey::P256(key)) => { 129 + let pk = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_FIXED, key); 130 + pk.verify(message, &signature) 131 + .map_err(|_| Error::SignatureFailed)?; 132 + } 133 + _ => unimplemented!(), 134 + } 135 + 136 + let claims = parse(claims)?; 137 + 138 + Ok(Token { header, claims }) 139 + } 140 + 141 + #[cfg(test)] 142 + mod tests { 143 + use super::{Algorithm, PublicKey, Token, Type}; 144 + use identity::Did; 145 + 146 + #[test] 147 + fn can_split_token() { 148 + use super::split_token; 149 + 150 + assert!(split_token(b"").is_err()); 151 + assert!(split_token(b"header").is_err()); 152 + assert!(split_token(b"header.claims").is_err()); 153 + 154 + let (h, c, s) = split_token(b"header.claims.signature").unwrap(); 155 + assert_eq!(h, b"header"); 156 + assert_eq!(c, b"claims"); 157 + assert_eq!(s, b"signature"); 158 + } 159 + 160 + const TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpYXQiOjE3NTg2NjE4ODUsImlzcyI6ImRpZDpwbGM6NjVnaGE0dDNhdnBmcHptdnBid292c3M3IiwiYXVkIjoiZGlkOndlYjpnb3JkaWFuLWRldjo1NTU1IiwiZXhwIjoxNzU4NjYxOTQ1LCJseG0iOiJzaC50YW5nbGVkLnJlcG8uY3JlYXRlIiwianRpIjoiY2Y0ZDE5YTIwNDE0YWMzMjk2NTI3NzBkYzIzYjUzNTYifQ.llMTh_dC72uV3A9STs8yTFAo8jO9XUJnK-m8eA4wZ0EZXeLpxQn3oviFH22eh9_SEKtj9y0YXCfWCafVJre8qg"; 161 + 162 + #[test] 163 + fn can_decode_token() { 164 + let Token { header, claims } = Token::decode_unverified(TOKEN).unwrap(); 165 + 166 + assert_eq!(header.typ, Type::JWT); 167 + assert_eq!(header.alg, Algorithm::ES256K); 168 + assert_eq!(claims.aud, Did::from_static("did:web:gordian-dev:5555")); 169 + assert_eq!( 170 + claims.iss, 171 + Did::from_static("did:plc:65gha4t3avpfpzmvpbwovss7") 172 + ); 173 + } 174 + 175 + #[test] 176 + fn can_verify_token() { 177 + const KEY: &str = "zQ3shNWn4uG62Nv3dkggV5dGiN7bHK2w2tX2QxtKpVCvDK4Ff"; 178 + let key = PublicKey::from_multibase(KEY).unwrap(); 179 + 180 + let Token { header, claims } = Token::decode(TOKEN, &key).unwrap(); 181 + 182 + assert_eq!(header.typ, Type::JWT); 183 + assert_eq!(header.alg, Algorithm::ES256K); 184 + assert_eq!(claims.aud, Did::from_static("did:web:gordian-dev:5555")); 185 + } 186 + }
+67
crates/service_auth/src/key.rs
··· 1 + use identity::{Did, VerificationMethod}; 2 + 3 + pub enum PublicKey { 4 + /// Secp256k1 Public key (compressed) 5 + K256([u8; 33]), 6 + /// Secp256r1 Public key (compressed) 7 + P256([u8; 33]), 8 + } 9 + 10 + #[derive(Debug, thiserror::Error)] 11 + pub enum KeyError { 12 + #[error("{0}")] 13 + Decode(#[from] multibase::Error), 14 + #[error("The provided key material is too short")] 15 + KeyLength, 16 + #[error("Key not supported")] 17 + UnsupportedKey, 18 + } 19 + 20 + impl PublicKey { 21 + /// Parse a key from a 'did:key:...' 22 + /// 23 + /// # Example 24 + /// ```rust 25 + /// use service_auth::{Did, PublicKey}; 26 + /// let did = Did::from_static("did:key:zQ3shNWn4uG62Nv3dkggV5dGiN7bHK2w2tX2QxtKpVCvDK4Ff"); 27 + /// let key = PublicKey::from_did(&did).unwrap(); 28 + /// 29 + /// assert!(matches!(key, PublicKey::K256(_))); 30 + /// ``` 31 + pub fn from_did(did: &Did) -> Result<Self, KeyError> { 32 + if did.method() != "key" { 33 + // 34 + } 35 + Self::from_multibase(did.ident()) 36 + } 37 + 38 + pub fn from_multibase(k: &str) -> Result<Self, KeyError> { 39 + let (_, k) = multibase::decode(k)?; 40 + Self::from_multicodec(&k) 41 + } 42 + 43 + pub fn from_multicodec(k: &[u8]) -> Result<Self, KeyError> { 44 + match k.split_at_checked(2) { 45 + Some(([0xe7, 0x01], key)) => { 46 + Ok(Self::K256(key.try_into().map_err(|_| KeyError::KeyLength)?)) 47 + } 48 + Some(([0x80, 0x24], key)) => { 49 + Ok(Self::P256(key.try_into().map_err(|_| KeyError::KeyLength)?)) 50 + } 51 + _ => Err(KeyError::UnsupportedKey), 52 + } 53 + } 54 + } 55 + 56 + impl TryFrom<&VerificationMethod> for PublicKey { 57 + type Error = KeyError; 58 + 59 + fn try_from(value: &VerificationMethod) -> Result<Self, Self::Error> { 60 + match value { 61 + VerificationMethod::Multikey { 62 + public_key_multibase, 63 + .. 64 + } => Self::from_multibase(public_key_multibase), 65 + } 66 + } 67 + }
+63
crates/service_auth/src/lib.rs
··· 1 + use crate::jwt::{Claims, Token, decode, decode_unverified}; 2 + use axum::{ 3 + extract::{FromRef, FromRequestParts}, 4 + http::{StatusCode, header, request::Parts}, 5 + }; 6 + use identity::Resolver; 7 + use time::OffsetDateTime; 8 + 9 + pub mod jwt; 10 + pub mod key; 11 + 12 + pub use identity::Did; 13 + pub use key::*; 14 + 15 + pub struct ServiceAuthorization(pub Claims); 16 + 17 + impl<S: Sync> FromRequestParts<S> for ServiceAuthorization 18 + where 19 + Resolver: FromRef<S>, 20 + { 21 + type Rejection = StatusCode; 22 + 23 + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 24 + let authorization = parts 25 + .headers 26 + .get(header::AUTHORIZATION) 27 + .ok_or(StatusCode::UNAUTHORIZED)?; 28 + 29 + let authorization = 30 + std::str::from_utf8(authorization.as_bytes()).map_err(|_| StatusCode::UNAUTHORIZED)?; 31 + 32 + let bearer = authorization 33 + .strip_prefix("Bearer ") 34 + .ok_or(StatusCode::UNAUTHORIZED)?; 35 + 36 + let Token { claims, .. } = 37 + decode_unverified::<Claims>(bearer).map_err(|_| StatusCode::UNAUTHORIZED)?; 38 + 39 + let state = Resolver::from_ref(state); 40 + 41 + let document = state 42 + .resolve_identity(&claims.iss) 43 + .await 44 + .map_err(|_| StatusCode::UNAUTHORIZED)?; 45 + 46 + let verification_key = document 47 + .verification_method 48 + .first() 49 + .ok_or(StatusCode::UNAUTHORIZED)? 50 + .try_into() 51 + .map_err(|_| StatusCode::UNAUTHORIZED)?; 52 + 53 + let Token { claims, .. } = 54 + decode::<Claims>(bearer, &verification_key).map_err(|_| StatusCode::UNAUTHORIZED)?; 55 + 56 + if !(claims.iat..claims.exp).contains(&OffsetDateTime::now_utc().unix_timestamp()) { 57 + tracing::error!(?claims, "jwt token expired"); 58 + return Err(StatusCode::UNAUTHORIZED)?; 59 + } 60 + 61 + Ok(Self(claims)) 62 + } 63 + }
+13
crates/xrpc/Cargo.toml
··· 1 + [package] 2 + name = "xrpc" 3 + version.workspace = true 4 + authors.workspace = true 5 + edition.workspace = true 6 + publish.workspace = true 7 + 8 + [lib] 9 + proc-macro = true 10 + 11 + [dependencies] 12 + syn = "2.0" 13 + quote = "1.0"
+3
crates/xrpc/readme.md
··· 1 + # xrpc_macro 2 + 3 + Helpers for raw-dogging XRPC APIs in axum.
+98
crates/xrpc/src/lib.rs
··· 1 + use proc_macro::TokenStream; 2 + use quote::quote; 3 + use syn::{Generics, ItemFn, LitStr, Signature, parse::Parse}; 4 + 5 + #[proc_macro_attribute] 6 + pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream { 7 + let attr = syn::parse_macro_input!(attr as AttributeInput); 8 + let AttributeInput { nsid } = attr; 9 + 10 + let input = syn::parse_macro_input!(item as ItemFn); 11 + 12 + let ItemFn { 13 + attrs, 14 + vis, 15 + sig, 16 + block, 17 + } = input; 18 + 19 + let statements = block.stmts; 20 + 21 + let Signature { 22 + ident, 23 + inputs, 24 + output, 25 + generics, 26 + asyncness, 27 + .. 28 + } = sig; 29 + 30 + let Generics { where_clause, .. } = generics; 31 + 32 + quote!( 33 + #vis fn #ident<S: Clone + Send + Sync + 'static>() -> axum::Router<S> #where_clause { 34 + const NSID: &str = #nsid; 35 + 36 + #(#attrs)* 37 + #vis #asyncness fn #ident(#inputs) #output { 38 + #(#statements)* 39 + } 40 + 41 + axum::Router::<S>::new().route(concat!("/", #nsid), axum::routing::get(#ident)) 42 + } 43 + ) 44 + .into() 45 + } 46 + 47 + #[proc_macro_attribute] 48 + pub fn method(attr: TokenStream, item: TokenStream) -> TokenStream { 49 + let attr = syn::parse_macro_input!(attr as AttributeInput); 50 + let AttributeInput { nsid } = attr; 51 + 52 + let input = syn::parse_macro_input!(item as ItemFn); 53 + 54 + let ItemFn { 55 + attrs, 56 + vis, 57 + sig, 58 + block, 59 + } = input; 60 + 61 + let statements = block.stmts; 62 + 63 + let Signature { 64 + ident, 65 + inputs, 66 + output, 67 + generics, 68 + .. 69 + } = sig; 70 + 71 + let Generics { where_clause, .. } = generics; 72 + 73 + quote!( 74 + #vis fn #ident<S: Clone + Send + Sync + 'static>() -> axum::Router<S> #where_clause { 75 + const NSID: &str = #nsid; 76 + 77 + #(#attrs)* 78 + #vis async fn #ident(#inputs) #output { 79 + #(#statements)* 80 + } 81 + 82 + axum::Router::<S>::new().route(concat!("/", #nsid), axum::routing::post(#ident)) 83 + } 84 + ) 85 + .into() 86 + } 87 + 88 + struct AttributeInput { 89 + nsid: LitStr, 90 + } 91 + 92 + impl Parse for AttributeInput { 93 + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { 94 + Ok(Self { 95 + nsid: input.parse()?, 96 + }) 97 + } 98 + }
+8
justfile
··· 1 + host := "helr01:gordian-knot" 2 + bin := "gordian-knot" 3 + 4 + deployffs: 5 + # cargo build --release --package knot 6 + incus exec {{host}} -- unlink /usr/bin/{{bin}} 7 + incus file push target/release/{{bin}} {{host}}/usr/bin/{{bin}} 8 + incus exec {{host}} -- systemctl restart gordian.service