Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

bye rust cli

-13483
-404
binaries/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>Wisp CLI - Download</title> 7 - <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" /> 8 - <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" media="(prefers-color-scheme: dark)" /> 9 - <style> 10 - :root { 11 - /* Light theme */ 12 - --demo-bg: #eeeeee; 13 - --demo-text: #1a1a1a; 14 - --demo-text-secondary: #666; 15 - --demo-border: #ddd; 16 - --demo-input-bg: #fff; 17 - --demo-button-bg: #0066cc; 18 - --demo-button-text: #fff; 19 - --demo-code-bg: #f5f5f5; 20 - --demo-code-border: #e0e0e0; 21 - --demo-hr: #e0e0e0; 22 - } 23 - 24 - /* Dark theme */ 25 - @media (prefers-color-scheme: dark) { 26 - :root { 27 - --demo-bg: #1a1a1a; 28 - --demo-text: #e0e0e0; 29 - --demo-text-secondary: #999; 30 - --demo-border: #444; 31 - --demo-input-bg: #2a2a2a; 32 - --demo-button-bg: #0066cc; 33 - --demo-button-text: #fff; 34 - --demo-code-bg: #2a2a2a; 35 - --demo-code-border: #444; 36 - --demo-hr: #444; 37 - } 38 - } 39 - 40 - * { 41 - margin: 0; 42 - padding: 0; 43 - box-sizing: border-box; 44 - } 45 - 46 - body { 47 - margin: 0; 48 - padding: 0; 49 - background: var(--demo-bg); 50 - color: var(--demo-text); 51 - transition: background-color 200ms ease, color 200ms ease; 52 - font-family: system-ui, sans-serif; 53 - line-height: 1.6; 54 - } 55 - 56 - .container { 57 - max-width: 860px; 58 - margin: 40px auto; 59 - padding: 0 20px; 60 - min-height: 100vh; 61 - } 62 - 63 - h1 { 64 - margin-top: 0; 65 - color: var(--demo-text); 66 - font-size: 2.5rem; 67 - margin-bottom: 0.5rem; 68 - } 69 - 70 - .subtitle { 71 - color: var(--demo-text-secondary); 72 - font-size: 1.1rem; 73 - margin-bottom: 2rem; 74 - line-height: 1.4; 75 - } 76 - 77 - .description { 78 - margin-bottom: 2rem; 79 - line-height: 1.6; 80 - color: var(--demo-text); 81 - } 82 - 83 - hr { 84 - margin: 32px 0; 85 - border: none; 86 - border-top: 1px solid var(--demo-hr); 87 - } 88 - 89 - h2 { 90 - color: var(--demo-text); 91 - margin-bottom: 1rem; 92 - margin-top: 2rem; 93 - font-size: 1.5rem; 94 - } 95 - 96 - h3 { 97 - color: var(--demo-text); 98 - margin-bottom: 0.75rem; 99 - margin-top: 1.5rem; 100 - font-size: 1.2rem; 101 - } 102 - 103 - .downloads { 104 - background: var(--demo-code-bg); 105 - border: 1px solid var(--demo-code-border); 106 - border-radius: 8px; 107 - padding: 2rem; 108 - margin-bottom: 2rem; 109 - } 110 - 111 - .downloads h2 { 112 - margin-top: 0; 113 - } 114 - 115 - .download-link { 116 - display: block; 117 - background: var(--demo-button-bg); 118 - color: var(--demo-button-text); 119 - text-decoration: none; 120 - padding: 12px 16px; 121 - border-radius: 8px; 122 - margin-bottom: 8px; 123 - transition: opacity 0.2s; 124 - font-family: 'Monaco', 'Menlo', 'Courier New', monospace; 125 - font-size: 0.9rem; 126 - } 127 - 128 - .download-link:hover { 129 - opacity: 0.85; 130 - } 131 - 132 - .platform { 133 - font-weight: bold; 134 - margin-right: 0.5rem; 135 - } 136 - 137 - .cicd-section { 138 - margin-top: 2rem; 139 - } 140 - 141 - .cicd-section h2 { 142 - margin-top: 0; 143 - } 144 - 145 - .cicd-section > p { 146 - color: var(--demo-text-secondary); 147 - margin-bottom: 1rem; 148 - } 149 - 150 - pre { 151 - background: var(--demo-code-bg) !important; 152 - border: 1px solid var(--demo-code-border); 153 - padding: 1.5rem !important; 154 - border-radius: 8px; 155 - overflow-x: auto; 156 - font-size: 0.85rem; 157 - line-height: 1.5; 158 - margin: 1rem 0; 159 - } 160 - 161 - pre code { 162 - color: var(--demo-text); 163 - background: none !important; 164 - padding: 0 !important; 165 - text-shadow: none !important; 166 - } 167 - 168 - code { 169 - font-family: 'Monaco', 'Menlo', 'Courier New', monospace; 170 - } 171 - 172 - .note { 173 - background: var(--demo-code-bg); 174 - border-left: 4px solid var(--demo-button-bg); 175 - border: 1px solid var(--demo-code-border); 176 - border-left: 4px solid var(--demo-button-bg); 177 - padding: 1rem; 178 - margin-top: 1rem; 179 - border-radius: 4px; 180 - } 181 - 182 - .note strong { 183 - color: var(--demo-text); 184 - } 185 - 186 - .note code { 187 - background: var(--demo-input-bg); 188 - padding: 2px 6px; 189 - border-radius: 4px; 190 - border: 1px solid var(--demo-border); 191 - } 192 - 193 - a { 194 - color: var(--demo-button-bg); 195 - text-decoration: none; 196 - } 197 - 198 - a:hover { 199 - text-decoration: underline; 200 - } 201 - 202 - .features { 203 - margin: 2rem 0; 204 - } 205 - 206 - .features ul { 207 - list-style: none; 208 - padding-left: 0; 209 - } 210 - 211 - .features li { 212 - padding: 0.5rem 0; 213 - padding-left: 1.5rem; 214 - position: relative; 215 - line-height: 1.6; 216 - } 217 - 218 - .features li:before { 219 - content: "✓"; 220 - position: absolute; 221 - left: 0; 222 - color: var(--demo-button-bg); 223 - font-weight: bold; 224 - } 225 - 226 - .footer { 227 - margin-top: 3rem; 228 - padding-top: 2rem; 229 - border-top: 1px solid var(--demo-hr); 230 - text-align: center; 231 - color: var(--demo-text-secondary); 232 - } 233 - </style> 234 - </head> 235 - <body> 236 - <div class="container"> 237 - <h1>Wisp CLI <span style="font-size: 1.5rem; color: var(--demo-text-secondary);">v0.5.0</span></h1> 238 - <p class="subtitle">Deploy static sites to the AT Protocol</p> 239 - 240 - <div class="description"> 241 - <p> 242 - The Wisp CLI is a command-line tool for deploying static websites directly to your AT Protocol account. 243 - Host your sites on <a href="https://wisp.place" target="_blank">wisp.place</a> with full ownership and control, 244 - backed by the decentralized AT Protocol. 245 - </p> 246 - </div> 247 - 248 - <div class="features"> 249 - <h2>Features</h2> 250 - <ul> 251 - <li><strong>Deploy:</strong> Push static sites directly from your terminal</li> 252 - <li><strong>Pull:</strong> Download sites from the PDS for development or backup</li> 253 - <li><strong>Serve:</strong> Run a local server with real-time firehose updates</li> 254 - <li>Authenticate with app password or OAuth</li> 255 - </ul> 256 - </div> 257 - 258 - <div class="downloads"> 259 - <h2>Download v0.5.0</h2> 260 - <a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-darwin-universal" class="download-link" download> 261 - <span class="platform">macOS (Universal):</span> wisp-cli-darwin-universal 262 - </a> 263 - <a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" class="download-link" download> 264 - <span class="platform">macOS (Apple Silicon):</span> wisp-cli-aarch64-darwin 265 - </a> 266 - <a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-darwin-x86_64" class="download-link" download> 267 - <span class="platform">macOS (Intel):</span> wisp-cli-darwin-x86_64 268 - </a> 269 - <a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-linux" class="download-link" download> 270 - <span class="platform">Linux (ARM64):</span> wisp-cli-aarch64-linux 271 - </a> 272 - <a href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux" class="download-link" download> 273 - <span class="platform">Linux (x86_64):</span> wisp-cli-x86_64-linux 274 - </a> 275 - <h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">SHA-1 Checksums</h3> 276 - <pre style="font-size: 0.75rem; padding: 1rem;"><code class="language-bash">7d0cc968d2a130158c3b204d9a15bcc29b8af252 wisp-cli-darwin-universal 277 - 912f8f99c2b55ca6ad75e0a89903688bd2c1cb4b wisp-cli-aarch64-darwin 278 - 40d26d4a9c058e42e7911a195c3e078e8a4b5e82 wisp-cli-darwin-x86_64 279 - ef1992d8850f8fef1d719e4e8fab5431475c352e wisp-cli-aarch64-linux 280 - 3018dde8fec495abcae044079564ced93cdeb4f8 wisp-cli-x86_64-linux</code></pre> 281 - </div> 282 - 283 - <div class="cicd-section"> 284 - <h2>CI/CD Integration</h2> 285 - <p>Deploy automatically on every push using <a href="https://blog.tangled.org/ci" target="_blank">Tangled Spindle</a>:</p> 286 - 287 - <pre><code class="language-yaml">when: 288 - - event: ['push'] 289 - branch: ['main'] 290 - - event: ['manual'] 291 - 292 - engine: 'nixery' 293 - 294 - clone: 295 - skip: false 296 - depth: 1 297 - submodules: false 298 - 299 - dependencies: 300 - nixpkgs: 301 - - nodejs 302 - - coreutils 303 - - curl 304 - github:NixOS/nixpkgs/nixpkgs-unstable: 305 - - bun 306 - 307 - environment: 308 - SITE_PATH: 'dist' 309 - SITE_NAME: 'my-site' 310 - WISP_HANDLE: 'your-handle.bsky.social' 311 - 312 - steps: 313 - - name: build site 314 - command: | 315 - export PATH="$HOME/.nix-profile/bin:$PATH" 316 - 317 - # regenerate lockfile, https://github.com/npm/cli/pull/8184 makes rolldown not install 318 - rm package-lock.json bun.lock 319 - bun install @rolldown/binding-linux-arm64-gnu --save-optional 320 - bun install 321 - 322 - # run directly with bun because of shebang issues in nix 323 - bun node_modules/.bin/vite build 324 - 325 - - name: deploy to wisp 326 - command: | 327 - # Download Wisp CLI 328 - curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 329 - chmod +x wisp-cli 330 - 331 - # Deploy to Wisp 332 - ./wisp-cli \ 333 - "$WISP_HANDLE" \ 334 - --path "$SITE_PATH" \ 335 - --site "$SITE_NAME" \ 336 - --password "$WISP_APP_PASSWORD"</code></pre> 337 - 338 - <div class="note"> 339 - <strong>Note:</strong> Set <code>WISP_APP_PASSWORD</code> as a secret in your Tangled Spindle repository settings. 340 - Generate an app password from your AT Protocol account settings. 341 - </div> 342 - </div> 343 - 344 - <div class="cicd-section"> 345 - <h2>Basic Usage</h2> 346 - 347 - <h3>Deploy a Site</h3> 348 - <pre><code class="language-bash"># Download and make executable 349 - curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin 350 - chmod +x wisp-cli-aarch64-darwin 351 - 352 - # Deploy your site 353 - ./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \ 354 - --path ./dist \ 355 - --site my-site \ 356 - 357 - # Your site will be available at: 358 - # https://sites.wisp.place/your-handle/my-site</code></pre> 359 - 360 - <h3>Pull a Site from PDS</h3> 361 - <p style="color: var(--demo-text-secondary); margin-bottom: 0.5rem;">Download a site from the PDS to your local machine (uses OAuth authentication):</p> 362 - <pre><code class="language-bash"># Pull a site to a specific directory 363 - wisp-cli pull your-handle.bsky.social \ 364 - --site my-site \ 365 - --output ./my-site 366 - 367 - # Pull to current directory 368 - wisp-cli pull your-handle.bsky.social \ 369 - --site my-site 370 - </code></pre> 371 - <h3>Serve a Site Locally with Real-Time Updates</h3> 372 - <p style="color: var(--demo-text-secondary); margin-bottom: 0.5rem;">Run a local server that monitors the firehose for real-time updates (uses OAuth authentication):</p> 373 - <pre><code class="language-bash"># Serve on http://localhost:8080 (default) 374 - wisp-cli serve your-handle.bsky.social \ 375 - --site my-site 376 - 377 - # Serve on a custom port 378 - wisp-cli serve your-handle.bsky.social \ 379 - --site my-site \ 380 - --port 3000 381 - 382 - # Downloads site, serves it, and watches firehose for live updates! 383 - 384 - # Enable SPA mode (serve index.html for all routes) 385 - wisp-cli serve your-handle.bsky.social \ 386 - --site my-site \ 387 - --spa 388 - 389 - # Enable directory listing for paths without index files 390 - wisp-cli serve your-handle.bsky.social \ 391 - --site my-site \ 392 - --directory</code></pre> 393 - </div> 394 - 395 - <div class="footer"> 396 - <p>Learn more at <a href="https://wisp.place">wisp.place</a></p> 397 - </div> 398 - </div> 399 - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script> 400 - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script> 401 - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script> 402 - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script> 403 - </body> 404 - </html>
binaries/wisp-cli-aarch64-darwin

This is a binary file and will not be displayed.

binaries/wisp-cli-aarch64-linux

This is a binary file and will not be displayed.

binaries/wisp-cli-darwin-arm64

This is a binary file and will not be displayed.

binaries/wisp-cli-darwin-universal

This is a binary file and will not be displayed.

binaries/wisp-cli-darwin-x86_64

This is a binary file and will not be displayed.

binaries/wisp-cli-x86_64-linux

This is a binary file and will not be displayed.

-25
rust-cli/.gitignore
··· 1 - test/ 2 - .DS_STORE 3 - jacquard/ 4 - binaries/ 5 - # Generated by Cargo 6 - # will have compiled files and executables 7 - debug 8 - target 9 - 10 - # These are backup files generated by rustfmt 11 - **/*.rs.bk 12 - 13 - # MSVC Windows builds of rustc generate these, which store debugging information 14 - *.pdb 15 - 16 - # Generated by cargo mutants 17 - # Contains mutation testing data 18 - **/mutants.out*/ 19 - 20 - # RustRover 21 - # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 22 - # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 23 - # and can be added to the global gitignore or merged into this file. For a more nuclear 24 - # option (not recommended) you can uncomment the following to ignore the entire idea folder. 25 - #.idea/
-5138
rust-cli/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.25.1" 8 - source = "registry+https://github.com/rust-lang/crates.io-index" 9 - checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" 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 = "adler32" 22 - version = "1.2.0" 23 - source = "registry+https://github.com/rust-lang/crates.io-index" 24 - checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 25 - 26 - [[package]] 27 - name = "aho-corasick" 28 - version = "1.1.4" 29 - source = "registry+https://github.com/rust-lang/crates.io-index" 30 - checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 31 - dependencies = [ 32 - "memchr", 33 - ] 34 - 35 - [[package]] 36 - name = "aliasable" 37 - version = "0.1.3" 38 - source = "registry+https://github.com/rust-lang/crates.io-index" 39 - checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 40 - 41 - [[package]] 42 - name = "alloc-no-stdlib" 43 - version = "2.0.4" 44 - source = "registry+https://github.com/rust-lang/crates.io-index" 45 - checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 46 - 47 - [[package]] 48 - name = "alloc-stdlib" 49 - version = "0.2.2" 50 - source = "registry+https://github.com/rust-lang/crates.io-index" 51 - checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 52 - dependencies = [ 53 - "alloc-no-stdlib", 54 - ] 55 - 56 - [[package]] 57 - name = "allocator-api2" 58 - version = "0.2.21" 59 - source = "registry+https://github.com/rust-lang/crates.io-index" 60 - checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 61 - 62 - [[package]] 63 - name = "android_system_properties" 64 - version = "0.1.5" 65 - source = "registry+https://github.com/rust-lang/crates.io-index" 66 - checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 67 - dependencies = [ 68 - "libc", 69 - ] 70 - 71 - [[package]] 72 - name = "anstream" 73 - version = "0.6.21" 74 - source = "registry+https://github.com/rust-lang/crates.io-index" 75 - checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 76 - dependencies = [ 77 - "anstyle", 78 - "anstyle-parse", 79 - "anstyle-query", 80 - "anstyle-wincon", 81 - "colorchoice", 82 - "is_terminal_polyfill", 83 - "utf8parse", 84 - ] 85 - 86 - [[package]] 87 - name = "anstyle" 88 - version = "1.0.13" 89 - source = "registry+https://github.com/rust-lang/crates.io-index" 90 - checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 91 - 92 - [[package]] 93 - name = "anstyle-parse" 94 - version = "0.2.7" 95 - source = "registry+https://github.com/rust-lang/crates.io-index" 96 - checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 97 - dependencies = [ 98 - "utf8parse", 99 - ] 100 - 101 - [[package]] 102 - name = "anstyle-query" 103 - version = "1.1.5" 104 - source = "registry+https://github.com/rust-lang/crates.io-index" 105 - checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 106 - dependencies = [ 107 - "windows-sys 0.61.2", 108 - ] 109 - 110 - [[package]] 111 - name = "anstyle-wincon" 112 - version = "3.0.11" 113 - source = "registry+https://github.com/rust-lang/crates.io-index" 114 - checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 115 - dependencies = [ 116 - "anstyle", 117 - "once_cell_polyfill", 118 - "windows-sys 0.61.2", 119 - ] 120 - 121 - [[package]] 122 - name = "ascii" 123 - version = "1.1.0" 124 - source = "registry+https://github.com/rust-lang/crates.io-index" 125 - checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" 126 - 127 - [[package]] 128 - name = "async-compression" 129 - version = "0.4.37" 130 - source = "registry+https://github.com/rust-lang/crates.io-index" 131 - checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" 132 - dependencies = [ 133 - "compression-codecs", 134 - "compression-core", 135 - "pin-project-lite", 136 - "tokio", 137 - ] 138 - 139 - [[package]] 140 - name = "async-trait" 141 - version = "0.1.89" 142 - source = "registry+https://github.com/rust-lang/crates.io-index" 143 - checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 144 - dependencies = [ 145 - "proc-macro2", 146 - "quote", 147 - "syn", 148 - ] 149 - 150 - [[package]] 151 - name = "atomic-polyfill" 152 - version = "1.0.3" 153 - source = "registry+https://github.com/rust-lang/crates.io-index" 154 - checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" 155 - dependencies = [ 156 - "critical-section", 157 - ] 158 - 159 - [[package]] 160 - name = "atomic-waker" 161 - version = "1.1.2" 162 - source = "registry+https://github.com/rust-lang/crates.io-index" 163 - checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 164 - 165 - [[package]] 166 - name = "autocfg" 167 - version = "1.5.0" 168 - source = "registry+https://github.com/rust-lang/crates.io-index" 169 - checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 170 - 171 - [[package]] 172 - name = "axum" 173 - version = "0.8.8" 174 - source = "registry+https://github.com/rust-lang/crates.io-index" 175 - checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" 176 - dependencies = [ 177 - "axum-core", 178 - "bytes", 179 - "form_urlencoded", 180 - "futures-util", 181 - "http", 182 - "http-body", 183 - "http-body-util", 184 - "hyper", 185 - "hyper-util", 186 - "itoa", 187 - "matchit", 188 - "memchr", 189 - "mime", 190 - "percent-encoding", 191 - "pin-project-lite", 192 - "serde_core", 193 - "serde_json", 194 - "serde_path_to_error", 195 - "serde_urlencoded", 196 - "sync_wrapper", 197 - "tokio", 198 - "tower", 199 - "tower-layer", 200 - "tower-service", 201 - "tracing", 202 - ] 203 - 204 - [[package]] 205 - name = "axum-core" 206 - version = "0.5.6" 207 - source = "registry+https://github.com/rust-lang/crates.io-index" 208 - checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" 209 - dependencies = [ 210 - "bytes", 211 - "futures-core", 212 - "http", 213 - "http-body", 214 - "http-body-util", 215 - "mime", 216 - "pin-project-lite", 217 - "sync_wrapper", 218 - "tower-layer", 219 - "tower-service", 220 - "tracing", 221 - ] 222 - 223 - [[package]] 224 - name = "backtrace" 225 - version = "0.3.76" 226 - source = "registry+https://github.com/rust-lang/crates.io-index" 227 - checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" 228 - dependencies = [ 229 - "addr2line", 230 - "cfg-if", 231 - "libc", 232 - "miniz_oxide", 233 - "object", 234 - "rustc-demangle", 235 - "windows-link", 236 - ] 237 - 238 - [[package]] 239 - name = "backtrace-ext" 240 - version = "0.2.1" 241 - source = "registry+https://github.com/rust-lang/crates.io-index" 242 - checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" 243 - dependencies = [ 244 - "backtrace", 245 - ] 246 - 247 - [[package]] 248 - name = "base-x" 249 - version = "0.2.11" 250 - source = "registry+https://github.com/rust-lang/crates.io-index" 251 - checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 252 - 253 - [[package]] 254 - name = "base16ct" 255 - version = "0.2.0" 256 - source = "registry+https://github.com/rust-lang/crates.io-index" 257 - checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 258 - 259 - [[package]] 260 - name = "base256emoji" 261 - version = "1.0.2" 262 - source = "registry+https://github.com/rust-lang/crates.io-index" 263 - checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 264 - dependencies = [ 265 - "const-str", 266 - "match-lookup", 267 - ] 268 - 269 - [[package]] 270 - name = "base64" 271 - version = "0.13.1" 272 - source = "registry+https://github.com/rust-lang/crates.io-index" 273 - checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 274 - 275 - [[package]] 276 - name = "base64" 277 - version = "0.22.1" 278 - source = "registry+https://github.com/rust-lang/crates.io-index" 279 - checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 280 - 281 - [[package]] 282 - name = "base64ct" 283 - version = "1.8.3" 284 - source = "registry+https://github.com/rust-lang/crates.io-index" 285 - checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 286 - 287 - [[package]] 288 - name = "bitflags" 289 - version = "2.10.0" 290 - source = "registry+https://github.com/rust-lang/crates.io-index" 291 - checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 292 - 293 - [[package]] 294 - name = "block-buffer" 295 - version = "0.10.4" 296 - source = "registry+https://github.com/rust-lang/crates.io-index" 297 - checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 298 - dependencies = [ 299 - "generic-array", 300 - ] 301 - 302 - [[package]] 303 - name = "bon" 304 - version = "3.8.2" 305 - source = "registry+https://github.com/rust-lang/crates.io-index" 306 - checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c" 307 - dependencies = [ 308 - "bon-macros", 309 - "rustversion", 310 - ] 311 - 312 - [[package]] 313 - name = "bon-macros" 314 - version = "3.8.2" 315 - source = "registry+https://github.com/rust-lang/crates.io-index" 316 - checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365" 317 - dependencies = [ 318 - "darling 0.23.0", 319 - "ident_case", 320 - "prettyplease", 321 - "proc-macro2", 322 - "quote", 323 - "rustversion", 324 - "syn", 325 - ] 326 - 327 - [[package]] 328 - name = "borsh" 329 - version = "1.6.0" 330 - source = "registry+https://github.com/rust-lang/crates.io-index" 331 - checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" 332 - dependencies = [ 333 - "cfg_aliases", 334 - ] 335 - 336 - [[package]] 337 - name = "brotli" 338 - version = "3.5.0" 339 - source = "registry+https://github.com/rust-lang/crates.io-index" 340 - checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" 341 - dependencies = [ 342 - "alloc-no-stdlib", 343 - "alloc-stdlib", 344 - "brotli-decompressor", 345 - ] 346 - 347 - [[package]] 348 - name = "brotli-decompressor" 349 - version = "2.5.1" 350 - source = "registry+https://github.com/rust-lang/crates.io-index" 351 - checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" 352 - dependencies = [ 353 - "alloc-no-stdlib", 354 - "alloc-stdlib", 355 - ] 356 - 357 - [[package]] 358 - name = "bstr" 359 - version = "1.12.1" 360 - source = "registry+https://github.com/rust-lang/crates.io-index" 361 - checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" 362 - dependencies = [ 363 - "memchr", 364 - "serde", 365 - ] 366 - 367 - [[package]] 368 - name = "buf_redux" 369 - version = "0.8.4" 370 - source = "registry+https://github.com/rust-lang/crates.io-index" 371 - checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" 372 - dependencies = [ 373 - "memchr", 374 - "safemem", 375 - ] 376 - 377 - [[package]] 378 - name = "bumpalo" 379 - version = "3.19.1" 380 - source = "registry+https://github.com/rust-lang/crates.io-index" 381 - checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 382 - 383 - [[package]] 384 - name = "byteorder" 385 - version = "1.5.0" 386 - source = "registry+https://github.com/rust-lang/crates.io-index" 387 - checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 388 - 389 - [[package]] 390 - name = "bytes" 391 - version = "1.11.0" 392 - source = "registry+https://github.com/rust-lang/crates.io-index" 393 - checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 394 - dependencies = [ 395 - "serde", 396 - ] 397 - 398 - [[package]] 399 - name = "cbor4ii" 400 - version = "0.2.14" 401 - source = "registry+https://github.com/rust-lang/crates.io-index" 402 - checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 403 - dependencies = [ 404 - "serde", 405 - ] 406 - 407 - [[package]] 408 - name = "cc" 409 - version = "1.2.54" 410 - source = "registry+https://github.com/rust-lang/crates.io-index" 411 - checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" 412 - dependencies = [ 413 - "find-msvc-tools", 414 - "shlex", 415 - ] 416 - 417 - [[package]] 418 - name = "cesu8" 419 - version = "1.1.0" 420 - source = "registry+https://github.com/rust-lang/crates.io-index" 421 - checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 422 - 423 - [[package]] 424 - name = "cfg-if" 425 - version = "1.0.4" 426 - source = "registry+https://github.com/rust-lang/crates.io-index" 427 - checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 428 - 429 - [[package]] 430 - name = "cfg_aliases" 431 - version = "0.2.1" 432 - source = "registry+https://github.com/rust-lang/crates.io-index" 433 - checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 434 - 435 - [[package]] 436 - name = "chrono" 437 - version = "0.4.43" 438 - source = "registry+https://github.com/rust-lang/crates.io-index" 439 - checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" 440 - dependencies = [ 441 - "iana-time-zone", 442 - "js-sys", 443 - "num-traits", 444 - "serde", 445 - "wasm-bindgen", 446 - "windows-link", 447 - ] 448 - 449 - [[package]] 450 - name = "chunked_transfer" 451 - version = "1.5.0" 452 - source = "registry+https://github.com/rust-lang/crates.io-index" 453 - checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" 454 - 455 - [[package]] 456 - name = "ciborium" 457 - version = "0.2.2" 458 - source = "registry+https://github.com/rust-lang/crates.io-index" 459 - checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 460 - dependencies = [ 461 - "ciborium-io", 462 - "ciborium-ll", 463 - "serde", 464 - ] 465 - 466 - [[package]] 467 - name = "ciborium-io" 468 - version = "0.2.2" 469 - source = "registry+https://github.com/rust-lang/crates.io-index" 470 - checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 471 - 472 - [[package]] 473 - name = "ciborium-ll" 474 - version = "0.2.2" 475 - source = "registry+https://github.com/rust-lang/crates.io-index" 476 - checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 477 - dependencies = [ 478 - "ciborium-io", 479 - "half", 480 - ] 481 - 482 - [[package]] 483 - name = "cid" 484 - version = "0.11.1" 485 - source = "registry+https://github.com/rust-lang/crates.io-index" 486 - checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 487 - dependencies = [ 488 - "core2", 489 - "multibase", 490 - "multihash", 491 - "serde", 492 - "serde_bytes", 493 - "unsigned-varint", 494 - ] 495 - 496 - [[package]] 497 - name = "clap" 498 - version = "4.5.54" 499 - source = "registry+https://github.com/rust-lang/crates.io-index" 500 - checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" 501 - dependencies = [ 502 - "clap_builder", 503 - "clap_derive", 504 - ] 505 - 506 - [[package]] 507 - name = "clap_builder" 508 - version = "4.5.54" 509 - source = "registry+https://github.com/rust-lang/crates.io-index" 510 - checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" 511 - dependencies = [ 512 - "anstream", 513 - "anstyle", 514 - "clap_lex", 515 - "strsim", 516 - ] 517 - 518 - [[package]] 519 - name = "clap_derive" 520 - version = "4.5.49" 521 - source = "registry+https://github.com/rust-lang/crates.io-index" 522 - checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 523 - dependencies = [ 524 - "heck 0.5.0", 525 - "proc-macro2", 526 - "quote", 527 - "syn", 528 - ] 529 - 530 - [[package]] 531 - name = "clap_lex" 532 - version = "0.7.7" 533 - source = "registry+https://github.com/rust-lang/crates.io-index" 534 - checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" 535 - 536 - [[package]] 537 - name = "cobs" 538 - version = "0.3.0" 539 - source = "registry+https://github.com/rust-lang/crates.io-index" 540 - checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" 541 - dependencies = [ 542 - "thiserror 2.0.18", 543 - ] 544 - 545 - [[package]] 546 - name = "colorchoice" 547 - version = "1.0.4" 548 - source = "registry+https://github.com/rust-lang/crates.io-index" 549 - checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 550 - 551 - [[package]] 552 - name = "combine" 553 - version = "4.6.7" 554 - source = "registry+https://github.com/rust-lang/crates.io-index" 555 - checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 556 - dependencies = [ 557 - "bytes", 558 - "memchr", 559 - ] 560 - 561 - [[package]] 562 - name = "compression-codecs" 563 - version = "0.4.36" 564 - source = "registry+https://github.com/rust-lang/crates.io-index" 565 - checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" 566 - dependencies = [ 567 - "compression-core", 568 - "flate2", 569 - "memchr", 570 - ] 571 - 572 - [[package]] 573 - name = "compression-core" 574 - version = "0.4.31" 575 - source = "registry+https://github.com/rust-lang/crates.io-index" 576 - checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" 577 - 578 - [[package]] 579 - name = "console" 580 - version = "0.15.11" 581 - source = "registry+https://github.com/rust-lang/crates.io-index" 582 - checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 583 - dependencies = [ 584 - "encode_unicode", 585 - "libc", 586 - "once_cell", 587 - "unicode-width 0.2.2", 588 - "windows-sys 0.59.0", 589 - ] 590 - 591 - [[package]] 592 - name = "const-oid" 593 - version = "0.9.6" 594 - source = "registry+https://github.com/rust-lang/crates.io-index" 595 - checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 596 - 597 - [[package]] 598 - name = "const-str" 599 - version = "0.4.3" 600 - source = "registry+https://github.com/rust-lang/crates.io-index" 601 - checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 602 - 603 - [[package]] 604 - name = "convert_case" 605 - version = "0.10.0" 606 - source = "registry+https://github.com/rust-lang/crates.io-index" 607 - checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" 608 - dependencies = [ 609 - "unicode-segmentation", 610 - ] 611 - 612 - [[package]] 613 - name = "cordyceps" 614 - version = "0.3.4" 615 - source = "registry+https://github.com/rust-lang/crates.io-index" 616 - checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" 617 - dependencies = [ 618 - "loom", 619 - "tracing", 620 - ] 621 - 622 - [[package]] 623 - name = "core-foundation" 624 - version = "0.9.4" 625 - source = "registry+https://github.com/rust-lang/crates.io-index" 626 - checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 627 - dependencies = [ 628 - "core-foundation-sys", 629 - "libc", 630 - ] 631 - 632 - [[package]] 633 - name = "core-foundation" 634 - version = "0.10.1" 635 - source = "registry+https://github.com/rust-lang/crates.io-index" 636 - checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 637 - dependencies = [ 638 - "core-foundation-sys", 639 - "libc", 640 - ] 641 - 642 - [[package]] 643 - name = "core-foundation-sys" 644 - version = "0.8.7" 645 - source = "registry+https://github.com/rust-lang/crates.io-index" 646 - checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 647 - 648 - [[package]] 649 - name = "core2" 650 - version = "0.4.0" 651 - source = "registry+https://github.com/rust-lang/crates.io-index" 652 - checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 653 - dependencies = [ 654 - "memchr", 655 - ] 656 - 657 - [[package]] 658 - name = "cpufeatures" 659 - version = "0.2.17" 660 - source = "registry+https://github.com/rust-lang/crates.io-index" 661 - checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 662 - dependencies = [ 663 - "libc", 664 - ] 665 - 666 - [[package]] 667 - name = "crc32fast" 668 - version = "1.5.0" 669 - source = "registry+https://github.com/rust-lang/crates.io-index" 670 - checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 671 - dependencies = [ 672 - "cfg-if", 673 - ] 674 - 675 - [[package]] 676 - name = "critical-section" 677 - version = "1.2.0" 678 - source = "registry+https://github.com/rust-lang/crates.io-index" 679 - checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 680 - 681 - [[package]] 682 - name = "crossbeam-channel" 683 - version = "0.5.15" 684 - source = "registry+https://github.com/rust-lang/crates.io-index" 685 - checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 686 - dependencies = [ 687 - "crossbeam-utils", 688 - ] 689 - 690 - [[package]] 691 - name = "crossbeam-deque" 692 - version = "0.8.6" 693 - source = "registry+https://github.com/rust-lang/crates.io-index" 694 - checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 695 - dependencies = [ 696 - "crossbeam-epoch", 697 - "crossbeam-utils", 698 - ] 699 - 700 - [[package]] 701 - name = "crossbeam-epoch" 702 - version = "0.9.18" 703 - source = "registry+https://github.com/rust-lang/crates.io-index" 704 - checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 705 - dependencies = [ 706 - "crossbeam-utils", 707 - ] 708 - 709 - [[package]] 710 - name = "crossbeam-utils" 711 - version = "0.8.21" 712 - source = "registry+https://github.com/rust-lang/crates.io-index" 713 - checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 714 - 715 - [[package]] 716 - name = "crunchy" 717 - version = "0.2.4" 718 - source = "registry+https://github.com/rust-lang/crates.io-index" 719 - checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 720 - 721 - [[package]] 722 - name = "crypto-bigint" 723 - version = "0.5.5" 724 - source = "registry+https://github.com/rust-lang/crates.io-index" 725 - checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 726 - dependencies = [ 727 - "generic-array", 728 - "rand_core 0.6.4", 729 - "subtle", 730 - "zeroize", 731 - ] 732 - 733 - [[package]] 734 - name = "crypto-common" 735 - version = "0.1.6" 736 - source = "registry+https://github.com/rust-lang/crates.io-index" 737 - checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 738 - dependencies = [ 739 - "generic-array", 740 - "typenum", 741 - ] 742 - 743 - [[package]] 744 - name = "darling" 745 - version = "0.21.3" 746 - source = "registry+https://github.com/rust-lang/crates.io-index" 747 - checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 748 - dependencies = [ 749 - "darling_core 0.21.3", 750 - "darling_macro 0.21.3", 751 - ] 752 - 753 - [[package]] 754 - name = "darling" 755 - version = "0.23.0" 756 - source = "registry+https://github.com/rust-lang/crates.io-index" 757 - checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" 758 - dependencies = [ 759 - "darling_core 0.23.0", 760 - "darling_macro 0.23.0", 761 - ] 762 - 763 - [[package]] 764 - name = "darling_core" 765 - version = "0.21.3" 766 - source = "registry+https://github.com/rust-lang/crates.io-index" 767 - checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 768 - dependencies = [ 769 - "fnv", 770 - "ident_case", 771 - "proc-macro2", 772 - "quote", 773 - "strsim", 774 - "syn", 775 - ] 776 - 777 - [[package]] 778 - name = "darling_core" 779 - version = "0.23.0" 780 - source = "registry+https://github.com/rust-lang/crates.io-index" 781 - checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" 782 - dependencies = [ 783 - "ident_case", 784 - "proc-macro2", 785 - "quote", 786 - "strsim", 787 - "syn", 788 - ] 789 - 790 - [[package]] 791 - name = "darling_macro" 792 - version = "0.21.3" 793 - source = "registry+https://github.com/rust-lang/crates.io-index" 794 - checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 795 - dependencies = [ 796 - "darling_core 0.21.3", 797 - "quote", 798 - "syn", 799 - ] 800 - 801 - [[package]] 802 - name = "darling_macro" 803 - version = "0.23.0" 804 - source = "registry+https://github.com/rust-lang/crates.io-index" 805 - checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" 806 - dependencies = [ 807 - "darling_core 0.23.0", 808 - "quote", 809 - "syn", 810 - ] 811 - 812 - [[package]] 813 - name = "dashmap" 814 - version = "6.1.0" 815 - source = "registry+https://github.com/rust-lang/crates.io-index" 816 - checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 817 - dependencies = [ 818 - "cfg-if", 819 - "crossbeam-utils", 820 - "hashbrown 0.14.5", 821 - "lock_api", 822 - "once_cell", 823 - "parking_lot_core", 824 - ] 825 - 826 - [[package]] 827 - name = "data-encoding" 828 - version = "2.10.0" 829 - source = "registry+https://github.com/rust-lang/crates.io-index" 830 - checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 831 - 832 - [[package]] 833 - name = "data-encoding-macro" 834 - version = "0.1.19" 835 - source = "registry+https://github.com/rust-lang/crates.io-index" 836 - checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 837 - dependencies = [ 838 - "data-encoding", 839 - "data-encoding-macro-internal", 840 - ] 841 - 842 - [[package]] 843 - name = "data-encoding-macro-internal" 844 - version = "0.1.17" 845 - source = "registry+https://github.com/rust-lang/crates.io-index" 846 - checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 847 - dependencies = [ 848 - "data-encoding", 849 - "syn", 850 - ] 851 - 852 - [[package]] 853 - name = "deflate" 854 - version = "1.0.0" 855 - source = "registry+https://github.com/rust-lang/crates.io-index" 856 - checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" 857 - dependencies = [ 858 - "adler32", 859 - "gzip-header", 860 - ] 861 - 862 - [[package]] 863 - name = "der" 864 - version = "0.7.10" 865 - source = "registry+https://github.com/rust-lang/crates.io-index" 866 - checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 867 - dependencies = [ 868 - "const-oid", 869 - "pem-rfc7468", 870 - "zeroize", 871 - ] 872 - 873 - [[package]] 874 - name = "deranged" 875 - version = "0.5.5" 876 - source = "registry+https://github.com/rust-lang/crates.io-index" 877 - checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 878 - dependencies = [ 879 - "powerfmt", 880 - ] 881 - 882 - [[package]] 883 - name = "derive_more" 884 - version = "1.0.0" 885 - source = "registry+https://github.com/rust-lang/crates.io-index" 886 - checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 887 - dependencies = [ 888 - "derive_more-impl 1.0.0", 889 - ] 890 - 891 - [[package]] 892 - name = "derive_more" 893 - version = "2.1.1" 894 - source = "registry+https://github.com/rust-lang/crates.io-index" 895 - checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" 896 - dependencies = [ 897 - "derive_more-impl 2.1.1", 898 - ] 899 - 900 - [[package]] 901 - name = "derive_more-impl" 902 - version = "1.0.0" 903 - source = "registry+https://github.com/rust-lang/crates.io-index" 904 - checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 905 - dependencies = [ 906 - "proc-macro2", 907 - "quote", 908 - "syn", 909 - "unicode-xid", 910 - ] 911 - 912 - [[package]] 913 - name = "derive_more-impl" 914 - version = "2.1.1" 915 - source = "registry+https://github.com/rust-lang/crates.io-index" 916 - checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" 917 - dependencies = [ 918 - "convert_case", 919 - "proc-macro2", 920 - "quote", 921 - "rustc_version", 922 - "syn", 923 - "unicode-xid", 924 - ] 925 - 926 - [[package]] 927 - name = "diatomic-waker" 928 - version = "0.2.3" 929 - source = "registry+https://github.com/rust-lang/crates.io-index" 930 - checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 931 - 932 - [[package]] 933 - name = "digest" 934 - version = "0.10.7" 935 - source = "registry+https://github.com/rust-lang/crates.io-index" 936 - checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 937 - dependencies = [ 938 - "block-buffer", 939 - "const-oid", 940 - "crypto-common", 941 - "subtle", 942 - ] 943 - 944 - [[package]] 945 - name = "dirs" 946 - version = "6.0.0" 947 - source = "registry+https://github.com/rust-lang/crates.io-index" 948 - checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 949 - dependencies = [ 950 - "dirs-sys", 951 - ] 952 - 953 - [[package]] 954 - name = "dirs-sys" 955 - version = "0.5.0" 956 - source = "registry+https://github.com/rust-lang/crates.io-index" 957 - checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 958 - dependencies = [ 959 - "libc", 960 - "option-ext", 961 - "redox_users", 962 - "windows-sys 0.61.2", 963 - ] 964 - 965 - [[package]] 966 - name = "displaydoc" 967 - version = "0.2.5" 968 - source = "registry+https://github.com/rust-lang/crates.io-index" 969 - checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 970 - dependencies = [ 971 - "proc-macro2", 972 - "quote", 973 - "syn", 974 - ] 975 - 976 - [[package]] 977 - name = "ecdsa" 978 - version = "0.16.9" 979 - source = "registry+https://github.com/rust-lang/crates.io-index" 980 - checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 981 - dependencies = [ 982 - "der", 983 - "digest", 984 - "elliptic-curve", 985 - "rfc6979", 986 - "signature", 987 - "spki", 988 - ] 989 - 990 - [[package]] 991 - name = "elliptic-curve" 992 - version = "0.13.8" 993 - source = "registry+https://github.com/rust-lang/crates.io-index" 994 - checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 995 - dependencies = [ 996 - "base16ct", 997 - "crypto-bigint", 998 - "digest", 999 - "ff", 1000 - "generic-array", 1001 - "group", 1002 - "pem-rfc7468", 1003 - "pkcs8", 1004 - "rand_core 0.6.4", 1005 - "sec1", 1006 - "subtle", 1007 - "zeroize", 1008 - ] 1009 - 1010 - [[package]] 1011 - name = "embedded-io" 1012 - version = "0.4.0" 1013 - source = "registry+https://github.com/rust-lang/crates.io-index" 1014 - checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" 1015 - 1016 - [[package]] 1017 - name = "embedded-io" 1018 - version = "0.6.1" 1019 - source = "registry+https://github.com/rust-lang/crates.io-index" 1020 - checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 1021 - 1022 - [[package]] 1023 - name = "encode_unicode" 1024 - version = "1.0.0" 1025 - source = "registry+https://github.com/rust-lang/crates.io-index" 1026 - checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 1027 - 1028 - [[package]] 1029 - name = "encoding_rs" 1030 - version = "0.8.35" 1031 - source = "registry+https://github.com/rust-lang/crates.io-index" 1032 - checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 1033 - dependencies = [ 1034 - "cfg-if", 1035 - ] 1036 - 1037 - [[package]] 1038 - name = "enum-as-inner" 1039 - version = "0.6.1" 1040 - source = "registry+https://github.com/rust-lang/crates.io-index" 1041 - checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 1042 - dependencies = [ 1043 - "heck 0.5.0", 1044 - "proc-macro2", 1045 - "quote", 1046 - "syn", 1047 - ] 1048 - 1049 - [[package]] 1050 - name = "equivalent" 1051 - version = "1.0.2" 1052 - source = "registry+https://github.com/rust-lang/crates.io-index" 1053 - checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 1054 - 1055 - [[package]] 1056 - name = "errno" 1057 - version = "0.3.14" 1058 - source = "registry+https://github.com/rust-lang/crates.io-index" 1059 - checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 1060 - dependencies = [ 1061 - "libc", 1062 - "windows-sys 0.61.2", 1063 - ] 1064 - 1065 - [[package]] 1066 - name = "fastrand" 1067 - version = "2.3.0" 1068 - source = "registry+https://github.com/rust-lang/crates.io-index" 1069 - checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 1070 - 1071 - [[package]] 1072 - name = "ff" 1073 - version = "0.13.1" 1074 - source = "registry+https://github.com/rust-lang/crates.io-index" 1075 - checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 1076 - dependencies = [ 1077 - "rand_core 0.6.4", 1078 - "subtle", 1079 - ] 1080 - 1081 - [[package]] 1082 - name = "filetime" 1083 - version = "0.2.27" 1084 - source = "registry+https://github.com/rust-lang/crates.io-index" 1085 - checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" 1086 - dependencies = [ 1087 - "cfg-if", 1088 - "libc", 1089 - "libredox", 1090 - ] 1091 - 1092 - [[package]] 1093 - name = "find-msvc-tools" 1094 - version = "0.1.8" 1095 - source = "registry+https://github.com/rust-lang/crates.io-index" 1096 - checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" 1097 - 1098 - [[package]] 1099 - name = "flate2" 1100 - version = "1.1.8" 1101 - source = "registry+https://github.com/rust-lang/crates.io-index" 1102 - checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" 1103 - dependencies = [ 1104 - "crc32fast", 1105 - "miniz_oxide", 1106 - ] 1107 - 1108 - [[package]] 1109 - name = "fnv" 1110 - version = "1.0.7" 1111 - source = "registry+https://github.com/rust-lang/crates.io-index" 1112 - checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1113 - 1114 - [[package]] 1115 - name = "foldhash" 1116 - version = "0.1.5" 1117 - source = "registry+https://github.com/rust-lang/crates.io-index" 1118 - checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1119 - 1120 - [[package]] 1121 - name = "form_urlencoded" 1122 - version = "1.2.2" 1123 - source = "registry+https://github.com/rust-lang/crates.io-index" 1124 - checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 1125 - dependencies = [ 1126 - "percent-encoding", 1127 - ] 1128 - 1129 - [[package]] 1130 - name = "futf" 1131 - version = "0.1.5" 1132 - source = "registry+https://github.com/rust-lang/crates.io-index" 1133 - checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 1134 - dependencies = [ 1135 - "mac", 1136 - "new_debug_unreachable", 1137 - ] 1138 - 1139 - [[package]] 1140 - name = "futures" 1141 - version = "0.3.31" 1142 - source = "registry+https://github.com/rust-lang/crates.io-index" 1143 - checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 1144 - dependencies = [ 1145 - "futures-channel", 1146 - "futures-core", 1147 - "futures-executor", 1148 - "futures-io", 1149 - "futures-sink", 1150 - "futures-task", 1151 - "futures-util", 1152 - ] 1153 - 1154 - [[package]] 1155 - name = "futures-buffered" 1156 - version = "0.2.12" 1157 - source = "registry+https://github.com/rust-lang/crates.io-index" 1158 - checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" 1159 - dependencies = [ 1160 - "cordyceps", 1161 - "diatomic-waker", 1162 - "futures-core", 1163 - "pin-project-lite", 1164 - "spin 0.10.0", 1165 - ] 1166 - 1167 - [[package]] 1168 - name = "futures-channel" 1169 - version = "0.3.31" 1170 - source = "registry+https://github.com/rust-lang/crates.io-index" 1171 - checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 1172 - dependencies = [ 1173 - "futures-core", 1174 - "futures-sink", 1175 - ] 1176 - 1177 - [[package]] 1178 - name = "futures-core" 1179 - version = "0.3.31" 1180 - source = "registry+https://github.com/rust-lang/crates.io-index" 1181 - checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 1182 - 1183 - [[package]] 1184 - name = "futures-executor" 1185 - version = "0.3.31" 1186 - source = "registry+https://github.com/rust-lang/crates.io-index" 1187 - checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 1188 - dependencies = [ 1189 - "futures-core", 1190 - "futures-task", 1191 - "futures-util", 1192 - ] 1193 - 1194 - [[package]] 1195 - name = "futures-io" 1196 - version = "0.3.31" 1197 - source = "registry+https://github.com/rust-lang/crates.io-index" 1198 - checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 1199 - 1200 - [[package]] 1201 - name = "futures-lite" 1202 - version = "2.6.1" 1203 - source = "registry+https://github.com/rust-lang/crates.io-index" 1204 - checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1205 - dependencies = [ 1206 - "fastrand", 1207 - "futures-core", 1208 - "futures-io", 1209 - "parking", 1210 - "pin-project-lite", 1211 - ] 1212 - 1213 - [[package]] 1214 - name = "futures-macro" 1215 - version = "0.3.31" 1216 - source = "registry+https://github.com/rust-lang/crates.io-index" 1217 - checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1218 - dependencies = [ 1219 - "proc-macro2", 1220 - "quote", 1221 - "syn", 1222 - ] 1223 - 1224 - [[package]] 1225 - name = "futures-sink" 1226 - version = "0.3.31" 1227 - source = "registry+https://github.com/rust-lang/crates.io-index" 1228 - checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1229 - 1230 - [[package]] 1231 - name = "futures-task" 1232 - version = "0.3.31" 1233 - source = "registry+https://github.com/rust-lang/crates.io-index" 1234 - checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1235 - 1236 - [[package]] 1237 - name = "futures-util" 1238 - version = "0.3.31" 1239 - source = "registry+https://github.com/rust-lang/crates.io-index" 1240 - checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 1241 - dependencies = [ 1242 - "futures-channel", 1243 - "futures-core", 1244 - "futures-io", 1245 - "futures-macro", 1246 - "futures-sink", 1247 - "futures-task", 1248 - "memchr", 1249 - "pin-project-lite", 1250 - "pin-utils", 1251 - "slab", 1252 - ] 1253 - 1254 - [[package]] 1255 - name = "generator" 1256 - version = "0.8.8" 1257 - source = "registry+https://github.com/rust-lang/crates.io-index" 1258 - checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" 1259 - dependencies = [ 1260 - "cc", 1261 - "cfg-if", 1262 - "libc", 1263 - "log", 1264 - "rustversion", 1265 - "windows-link", 1266 - "windows-result", 1267 - ] 1268 - 1269 - [[package]] 1270 - name = "generic-array" 1271 - version = "0.14.9" 1272 - source = "registry+https://github.com/rust-lang/crates.io-index" 1273 - checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 1274 - dependencies = [ 1275 - "typenum", 1276 - "version_check", 1277 - "zeroize", 1278 - ] 1279 - 1280 - [[package]] 1281 - name = "getrandom" 1282 - version = "0.2.17" 1283 - source = "registry+https://github.com/rust-lang/crates.io-index" 1284 - checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 1285 - dependencies = [ 1286 - "cfg-if", 1287 - "js-sys", 1288 - "libc", 1289 - "wasi", 1290 - "wasm-bindgen", 1291 - ] 1292 - 1293 - [[package]] 1294 - name = "getrandom" 1295 - version = "0.3.4" 1296 - source = "registry+https://github.com/rust-lang/crates.io-index" 1297 - checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 1298 - dependencies = [ 1299 - "cfg-if", 1300 - "js-sys", 1301 - "libc", 1302 - "r-efi", 1303 - "wasip2", 1304 - "wasm-bindgen", 1305 - ] 1306 - 1307 - [[package]] 1308 - name = "gimli" 1309 - version = "0.32.3" 1310 - source = "registry+https://github.com/rust-lang/crates.io-index" 1311 - checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" 1312 - 1313 - [[package]] 1314 - name = "globset" 1315 - version = "0.4.18" 1316 - source = "registry+https://github.com/rust-lang/crates.io-index" 1317 - checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" 1318 - dependencies = [ 1319 - "aho-corasick", 1320 - "bstr", 1321 - "log", 1322 - "regex-automata", 1323 - "regex-syntax", 1324 - ] 1325 - 1326 - [[package]] 1327 - name = "gloo-storage" 1328 - version = "0.3.0" 1329 - source = "registry+https://github.com/rust-lang/crates.io-index" 1330 - checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" 1331 - dependencies = [ 1332 - "gloo-utils", 1333 - "js-sys", 1334 - "serde", 1335 - "serde_json", 1336 - "thiserror 1.0.69", 1337 - "wasm-bindgen", 1338 - "web-sys", 1339 - ] 1340 - 1341 - [[package]] 1342 - name = "gloo-utils" 1343 - version = "0.2.0" 1344 - source = "registry+https://github.com/rust-lang/crates.io-index" 1345 - checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" 1346 - dependencies = [ 1347 - "js-sys", 1348 - "serde", 1349 - "serde_json", 1350 - "wasm-bindgen", 1351 - "web-sys", 1352 - ] 1353 - 1354 - [[package]] 1355 - name = "group" 1356 - version = "0.13.0" 1357 - source = "registry+https://github.com/rust-lang/crates.io-index" 1358 - checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1359 - dependencies = [ 1360 - "ff", 1361 - "rand_core 0.6.4", 1362 - "subtle", 1363 - ] 1364 - 1365 - [[package]] 1366 - name = "gzip-header" 1367 - version = "1.0.0" 1368 - source = "registry+https://github.com/rust-lang/crates.io-index" 1369 - checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" 1370 - dependencies = [ 1371 - "crc32fast", 1372 - ] 1373 - 1374 - [[package]] 1375 - name = "h2" 1376 - version = "0.4.13" 1377 - source = "registry+https://github.com/rust-lang/crates.io-index" 1378 - checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 1379 - dependencies = [ 1380 - "atomic-waker", 1381 - "bytes", 1382 - "fnv", 1383 - "futures-core", 1384 - "futures-sink", 1385 - "http", 1386 - "indexmap", 1387 - "slab", 1388 - "tokio", 1389 - "tokio-util", 1390 - "tracing", 1391 - ] 1392 - 1393 - [[package]] 1394 - name = "half" 1395 - version = "2.7.1" 1396 - source = "registry+https://github.com/rust-lang/crates.io-index" 1397 - checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 1398 - dependencies = [ 1399 - "cfg-if", 1400 - "crunchy", 1401 - "zerocopy", 1402 - ] 1403 - 1404 - [[package]] 1405 - name = "hash32" 1406 - version = "0.2.1" 1407 - source = "registry+https://github.com/rust-lang/crates.io-index" 1408 - checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" 1409 - dependencies = [ 1410 - "byteorder", 1411 - ] 1412 - 1413 - [[package]] 1414 - name = "hashbrown" 1415 - version = "0.14.5" 1416 - source = "registry+https://github.com/rust-lang/crates.io-index" 1417 - checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1418 - 1419 - [[package]] 1420 - name = "hashbrown" 1421 - version = "0.15.5" 1422 - source = "registry+https://github.com/rust-lang/crates.io-index" 1423 - checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1424 - dependencies = [ 1425 - "allocator-api2", 1426 - "equivalent", 1427 - "foldhash", 1428 - ] 1429 - 1430 - [[package]] 1431 - name = "hashbrown" 1432 - version = "0.16.1" 1433 - source = "registry+https://github.com/rust-lang/crates.io-index" 1434 - checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1435 - 1436 - [[package]] 1437 - name = "heapless" 1438 - version = "0.7.17" 1439 - source = "registry+https://github.com/rust-lang/crates.io-index" 1440 - checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" 1441 - dependencies = [ 1442 - "atomic-polyfill", 1443 - "hash32", 1444 - "rustc_version", 1445 - "serde", 1446 - "spin 0.9.8", 1447 - "stable_deref_trait", 1448 - ] 1449 - 1450 - [[package]] 1451 - name = "heck" 1452 - version = "0.4.1" 1453 - source = "registry+https://github.com/rust-lang/crates.io-index" 1454 - checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1455 - 1456 - [[package]] 1457 - name = "heck" 1458 - version = "0.5.0" 1459 - source = "registry+https://github.com/rust-lang/crates.io-index" 1460 - checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1461 - 1462 - [[package]] 1463 - name = "hermit-abi" 1464 - version = "0.5.2" 1465 - source = "registry+https://github.com/rust-lang/crates.io-index" 1466 - checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1467 - 1468 - [[package]] 1469 - name = "hex" 1470 - version = "0.4.3" 1471 - source = "registry+https://github.com/rust-lang/crates.io-index" 1472 - checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1473 - 1474 - [[package]] 1475 - name = "hickory-proto" 1476 - version = "0.24.4" 1477 - source = "registry+https://github.com/rust-lang/crates.io-index" 1478 - checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" 1479 - dependencies = [ 1480 - "async-trait", 1481 - "cfg-if", 1482 - "data-encoding", 1483 - "enum-as-inner", 1484 - "futures-channel", 1485 - "futures-io", 1486 - "futures-util", 1487 - "idna", 1488 - "ipnet", 1489 - "once_cell", 1490 - "rand 0.8.5", 1491 - "thiserror 1.0.69", 1492 - "tinyvec", 1493 - "tokio", 1494 - "tracing", 1495 - "url", 1496 - ] 1497 - 1498 - [[package]] 1499 - name = "hickory-resolver" 1500 - version = "0.24.4" 1501 - source = "registry+https://github.com/rust-lang/crates.io-index" 1502 - checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" 1503 - dependencies = [ 1504 - "cfg-if", 1505 - "futures-util", 1506 - "hickory-proto", 1507 - "ipconfig", 1508 - "lru-cache", 1509 - "once_cell", 1510 - "parking_lot", 1511 - "rand 0.8.5", 1512 - "resolv-conf", 1513 - "smallvec", 1514 - "thiserror 1.0.69", 1515 - "tokio", 1516 - "tracing", 1517 - ] 1518 - 1519 - [[package]] 1520 - name = "hmac" 1521 - version = "0.12.1" 1522 - source = "registry+https://github.com/rust-lang/crates.io-index" 1523 - checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1524 - dependencies = [ 1525 - "digest", 1526 - ] 1527 - 1528 - [[package]] 1529 - name = "html5ever" 1530 - version = "0.27.0" 1531 - source = "registry+https://github.com/rust-lang/crates.io-index" 1532 - checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 1533 - dependencies = [ 1534 - "log", 1535 - "mac", 1536 - "markup5ever", 1537 - "proc-macro2", 1538 - "quote", 1539 - "syn", 1540 - ] 1541 - 1542 - [[package]] 1543 - name = "http" 1544 - version = "1.4.0" 1545 - source = "registry+https://github.com/rust-lang/crates.io-index" 1546 - checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 1547 - dependencies = [ 1548 - "bytes", 1549 - "itoa", 1550 - ] 1551 - 1552 - [[package]] 1553 - name = "http-body" 1554 - version = "1.0.1" 1555 - source = "registry+https://github.com/rust-lang/crates.io-index" 1556 - checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1557 - dependencies = [ 1558 - "bytes", 1559 - "http", 1560 - ] 1561 - 1562 - [[package]] 1563 - name = "http-body-util" 1564 - version = "0.1.3" 1565 - source = "registry+https://github.com/rust-lang/crates.io-index" 1566 - checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1567 - dependencies = [ 1568 - "bytes", 1569 - "futures-core", 1570 - "http", 1571 - "http-body", 1572 - "pin-project-lite", 1573 - ] 1574 - 1575 - [[package]] 1576 - name = "http-range-header" 1577 - version = "0.4.2" 1578 - source = "registry+https://github.com/rust-lang/crates.io-index" 1579 - checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" 1580 - 1581 - [[package]] 1582 - name = "httparse" 1583 - version = "1.10.1" 1584 - source = "registry+https://github.com/rust-lang/crates.io-index" 1585 - checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1586 - 1587 - [[package]] 1588 - name = "httpdate" 1589 - version = "1.0.3" 1590 - source = "registry+https://github.com/rust-lang/crates.io-index" 1591 - checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1592 - 1593 - [[package]] 1594 - name = "hyper" 1595 - version = "1.8.1" 1596 - source = "registry+https://github.com/rust-lang/crates.io-index" 1597 - checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 1598 - dependencies = [ 1599 - "atomic-waker", 1600 - "bytes", 1601 - "futures-channel", 1602 - "futures-core", 1603 - "h2", 1604 - "http", 1605 - "http-body", 1606 - "httparse", 1607 - "httpdate", 1608 - "itoa", 1609 - "pin-project-lite", 1610 - "pin-utils", 1611 - "smallvec", 1612 - "tokio", 1613 - "want", 1614 - ] 1615 - 1616 - [[package]] 1617 - name = "hyper-rustls" 1618 - version = "0.27.7" 1619 - source = "registry+https://github.com/rust-lang/crates.io-index" 1620 - checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1621 - dependencies = [ 1622 - "http", 1623 - "hyper", 1624 - "hyper-util", 1625 - "rustls", 1626 - "rustls-pki-types", 1627 - "tokio", 1628 - "tokio-rustls", 1629 - "tower-service", 1630 - "webpki-roots", 1631 - ] 1632 - 1633 - [[package]] 1634 - name = "hyper-util" 1635 - version = "0.1.19" 1636 - source = "registry+https://github.com/rust-lang/crates.io-index" 1637 - checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1638 - dependencies = [ 1639 - "base64 0.22.1", 1640 - "bytes", 1641 - "futures-channel", 1642 - "futures-core", 1643 - "futures-util", 1644 - "http", 1645 - "http-body", 1646 - "hyper", 1647 - "ipnet", 1648 - "libc", 1649 - "percent-encoding", 1650 - "pin-project-lite", 1651 - "socket2 0.6.2", 1652 - "system-configuration", 1653 - "tokio", 1654 - "tower-service", 1655 - "tracing", 1656 - "windows-registry", 1657 - ] 1658 - 1659 - [[package]] 1660 - name = "iana-time-zone" 1661 - version = "0.1.64" 1662 - source = "registry+https://github.com/rust-lang/crates.io-index" 1663 - checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 1664 - dependencies = [ 1665 - "android_system_properties", 1666 - "core-foundation-sys", 1667 - "iana-time-zone-haiku", 1668 - "js-sys", 1669 - "log", 1670 - "wasm-bindgen", 1671 - "windows-core", 1672 - ] 1673 - 1674 - [[package]] 1675 - name = "iana-time-zone-haiku" 1676 - version = "0.1.2" 1677 - source = "registry+https://github.com/rust-lang/crates.io-index" 1678 - checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1679 - dependencies = [ 1680 - "cc", 1681 - ] 1682 - 1683 - [[package]] 1684 - name = "icu_collections" 1685 - version = "2.1.1" 1686 - source = "registry+https://github.com/rust-lang/crates.io-index" 1687 - checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 1688 - dependencies = [ 1689 - "displaydoc", 1690 - "potential_utf", 1691 - "yoke", 1692 - "zerofrom", 1693 - "zerovec", 1694 - ] 1695 - 1696 - [[package]] 1697 - name = "icu_locale_core" 1698 - version = "2.1.1" 1699 - source = "registry+https://github.com/rust-lang/crates.io-index" 1700 - checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 1701 - dependencies = [ 1702 - "displaydoc", 1703 - "litemap", 1704 - "tinystr", 1705 - "writeable", 1706 - "zerovec", 1707 - ] 1708 - 1709 - [[package]] 1710 - name = "icu_normalizer" 1711 - version = "2.1.1" 1712 - source = "registry+https://github.com/rust-lang/crates.io-index" 1713 - checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 1714 - dependencies = [ 1715 - "icu_collections", 1716 - "icu_normalizer_data", 1717 - "icu_properties", 1718 - "icu_provider", 1719 - "smallvec", 1720 - "zerovec", 1721 - ] 1722 - 1723 - [[package]] 1724 - name = "icu_normalizer_data" 1725 - version = "2.1.1" 1726 - source = "registry+https://github.com/rust-lang/crates.io-index" 1727 - checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 1728 - 1729 - [[package]] 1730 - name = "icu_properties" 1731 - version = "2.1.2" 1732 - source = "registry+https://github.com/rust-lang/crates.io-index" 1733 - checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 1734 - dependencies = [ 1735 - "icu_collections", 1736 - "icu_locale_core", 1737 - "icu_properties_data", 1738 - "icu_provider", 1739 - "zerotrie", 1740 - "zerovec", 1741 - ] 1742 - 1743 - [[package]] 1744 - name = "icu_properties_data" 1745 - version = "2.1.2" 1746 - source = "registry+https://github.com/rust-lang/crates.io-index" 1747 - checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 1748 - 1749 - [[package]] 1750 - name = "icu_provider" 1751 - version = "2.1.1" 1752 - source = "registry+https://github.com/rust-lang/crates.io-index" 1753 - checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 1754 - dependencies = [ 1755 - "displaydoc", 1756 - "icu_locale_core", 1757 - "writeable", 1758 - "yoke", 1759 - "zerofrom", 1760 - "zerotrie", 1761 - "zerovec", 1762 - ] 1763 - 1764 - [[package]] 1765 - name = "ident_case" 1766 - version = "1.0.1" 1767 - source = "registry+https://github.com/rust-lang/crates.io-index" 1768 - checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1769 - 1770 - [[package]] 1771 - name = "idna" 1772 - version = "1.1.0" 1773 - source = "registry+https://github.com/rust-lang/crates.io-index" 1774 - checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1775 - dependencies = [ 1776 - "idna_adapter", 1777 - "smallvec", 1778 - "utf8_iter", 1779 - ] 1780 - 1781 - [[package]] 1782 - name = "idna_adapter" 1783 - version = "1.2.1" 1784 - source = "registry+https://github.com/rust-lang/crates.io-index" 1785 - checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1786 - dependencies = [ 1787 - "icu_normalizer", 1788 - "icu_properties", 1789 - ] 1790 - 1791 - [[package]] 1792 - name = "ignore" 1793 - version = "0.4.25" 1794 - source = "registry+https://github.com/rust-lang/crates.io-index" 1795 - checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" 1796 - dependencies = [ 1797 - "crossbeam-deque", 1798 - "globset", 1799 - "log", 1800 - "memchr", 1801 - "regex-automata", 1802 - "same-file", 1803 - "walkdir", 1804 - "winapi-util", 1805 - ] 1806 - 1807 - [[package]] 1808 - name = "indexmap" 1809 - version = "2.13.0" 1810 - source = "registry+https://github.com/rust-lang/crates.io-index" 1811 - checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 1812 - dependencies = [ 1813 - "equivalent", 1814 - "hashbrown 0.16.1", 1815 - ] 1816 - 1817 - [[package]] 1818 - name = "indicatif" 1819 - version = "0.17.11" 1820 - source = "registry+https://github.com/rust-lang/crates.io-index" 1821 - checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 1822 - dependencies = [ 1823 - "console", 1824 - "number_prefix", 1825 - "portable-atomic", 1826 - "unicode-width 0.2.2", 1827 - "web-time", 1828 - ] 1829 - 1830 - [[package]] 1831 - name = "inventory" 1832 - version = "0.3.21" 1833 - source = "registry+https://github.com/rust-lang/crates.io-index" 1834 - checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" 1835 - dependencies = [ 1836 - "rustversion", 1837 - ] 1838 - 1839 - [[package]] 1840 - name = "ipconfig" 1841 - version = "0.3.2" 1842 - source = "registry+https://github.com/rust-lang/crates.io-index" 1843 - checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1844 - dependencies = [ 1845 - "socket2 0.5.10", 1846 - "widestring", 1847 - "windows-sys 0.48.0", 1848 - "winreg", 1849 - ] 1850 - 1851 - [[package]] 1852 - name = "ipld-core" 1853 - version = "0.4.2" 1854 - source = "registry+https://github.com/rust-lang/crates.io-index" 1855 - checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1856 - dependencies = [ 1857 - "cid", 1858 - "serde", 1859 - "serde_bytes", 1860 - ] 1861 - 1862 - [[package]] 1863 - name = "ipnet" 1864 - version = "2.11.0" 1865 - source = "registry+https://github.com/rust-lang/crates.io-index" 1866 - checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1867 - 1868 - [[package]] 1869 - name = "iri-string" 1870 - version = "0.7.10" 1871 - source = "registry+https://github.com/rust-lang/crates.io-index" 1872 - checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1873 - dependencies = [ 1874 - "memchr", 1875 - "serde", 1876 - ] 1877 - 1878 - [[package]] 1879 - name = "is_ci" 1880 - version = "1.2.0" 1881 - source = "registry+https://github.com/rust-lang/crates.io-index" 1882 - checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" 1883 - 1884 - [[package]] 1885 - name = "is_terminal_polyfill" 1886 - version = "1.70.2" 1887 - source = "registry+https://github.com/rust-lang/crates.io-index" 1888 - checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 1889 - 1890 - [[package]] 1891 - name = "itoa" 1892 - version = "1.0.17" 1893 - source = "registry+https://github.com/rust-lang/crates.io-index" 1894 - checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1895 - 1896 - [[package]] 1897 - name = "jacquard" 1898 - version = "0.9.5" 1899 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1900 - dependencies = [ 1901 - "bytes", 1902 - "getrandom 0.2.17", 1903 - "gloo-storage", 1904 - "http", 1905 - "jacquard-api", 1906 - "jacquard-common", 1907 - "jacquard-derive", 1908 - "jacquard-identity", 1909 - "jacquard-oauth", 1910 - "jose-jwk", 1911 - "miette", 1912 - "regex", 1913 - "regex-lite", 1914 - "reqwest", 1915 - "serde", 1916 - "serde_html_form", 1917 - "serde_json", 1918 - "smol_str", 1919 - "thiserror 2.0.18", 1920 - "tokio", 1921 - "trait-variant", 1922 - "url", 1923 - "webpage", 1924 - ] 1925 - 1926 - [[package]] 1927 - name = "jacquard-api" 1928 - version = "0.9.5" 1929 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1930 - dependencies = [ 1931 - "bon", 1932 - "bytes", 1933 - "jacquard-common", 1934 - "jacquard-derive", 1935 - "jacquard-lexicon", 1936 - "miette", 1937 - "rustversion", 1938 - "serde", 1939 - "serde_bytes", 1940 - "serde_ipld_dagcbor", 1941 - "thiserror 2.0.18", 1942 - "unicode-segmentation", 1943 - ] 1944 - 1945 - [[package]] 1946 - name = "jacquard-common" 1947 - version = "0.9.5" 1948 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1949 - dependencies = [ 1950 - "base64 0.22.1", 1951 - "bon", 1952 - "bytes", 1953 - "chrono", 1954 - "ciborium", 1955 - "ciborium-io", 1956 - "cid", 1957 - "futures", 1958 - "getrandom 0.2.17", 1959 - "getrandom 0.3.4", 1960 - "hashbrown 0.15.5", 1961 - "http", 1962 - "ipld-core", 1963 - "k256", 1964 - "maitake-sync", 1965 - "miette", 1966 - "multibase", 1967 - "multihash", 1968 - "n0-future 0.1.3", 1969 - "ouroboros", 1970 - "oxilangtag", 1971 - "p256", 1972 - "postcard", 1973 - "rand 0.9.2", 1974 - "regex", 1975 - "regex-automata", 1976 - "regex-lite", 1977 - "reqwest", 1978 - "serde", 1979 - "serde_bytes", 1980 - "serde_html_form", 1981 - "serde_ipld_dagcbor", 1982 - "serde_json", 1983 - "signature", 1984 - "smol_str", 1985 - "spin 0.10.0", 1986 - "thiserror 2.0.18", 1987 - "tokio", 1988 - "tokio-tungstenite-wasm", 1989 - "tokio-util", 1990 - "trait-variant", 1991 - "url", 1992 - ] 1993 - 1994 - [[package]] 1995 - name = "jacquard-derive" 1996 - version = "0.9.5" 1997 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 1998 - dependencies = [ 1999 - "heck 0.5.0", 2000 - "jacquard-lexicon", 2001 - "proc-macro2", 2002 - "quote", 2003 - "syn", 2004 - ] 2005 - 2006 - [[package]] 2007 - name = "jacquard-identity" 2008 - version = "0.9.5" 2009 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2010 - dependencies = [ 2011 - "bon", 2012 - "bytes", 2013 - "hickory-resolver", 2014 - "http", 2015 - "jacquard-api", 2016 - "jacquard-common", 2017 - "jacquard-lexicon", 2018 - "miette", 2019 - "mini-moka-wasm", 2020 - "n0-future 0.1.3", 2021 - "percent-encoding", 2022 - "reqwest", 2023 - "serde", 2024 - "serde_html_form", 2025 - "serde_json", 2026 - "thiserror 2.0.18", 2027 - "tokio", 2028 - "trait-variant", 2029 - "url", 2030 - "urlencoding", 2031 - ] 2032 - 2033 - [[package]] 2034 - name = "jacquard-lexicon" 2035 - version = "0.9.5" 2036 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2037 - dependencies = [ 2038 - "cid", 2039 - "dashmap", 2040 - "heck 0.5.0", 2041 - "inventory", 2042 - "jacquard-common", 2043 - "miette", 2044 - "multihash", 2045 - "prettyplease", 2046 - "proc-macro2", 2047 - "quote", 2048 - "serde", 2049 - "serde_ipld_dagcbor", 2050 - "serde_json", 2051 - "serde_path_to_error", 2052 - "serde_repr", 2053 - "serde_with", 2054 - "sha2", 2055 - "syn", 2056 - "thiserror 2.0.18", 2057 - "unicode-segmentation", 2058 - ] 2059 - 2060 - [[package]] 2061 - name = "jacquard-oauth" 2062 - version = "0.9.6" 2063 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2064 - dependencies = [ 2065 - "base64 0.22.1", 2066 - "bytes", 2067 - "chrono", 2068 - "dashmap", 2069 - "elliptic-curve", 2070 - "http", 2071 - "jacquard-common", 2072 - "jacquard-identity", 2073 - "jose-jwa", 2074 - "jose-jwk", 2075 - "miette", 2076 - "p256", 2077 - "rand 0.8.5", 2078 - "rouille", 2079 - "serde", 2080 - "serde_html_form", 2081 - "serde_json", 2082 - "sha2", 2083 - "smol_str", 2084 - "thiserror 2.0.18", 2085 - "tokio", 2086 - "trait-variant", 2087 - "url", 2088 - "webbrowser", 2089 - ] 2090 - 2091 - [[package]] 2092 - name = "jni" 2093 - version = "0.21.1" 2094 - source = "registry+https://github.com/rust-lang/crates.io-index" 2095 - checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 2096 - dependencies = [ 2097 - "cesu8", 2098 - "cfg-if", 2099 - "combine", 2100 - "jni-sys", 2101 - "log", 2102 - "thiserror 1.0.69", 2103 - "walkdir", 2104 - "windows-sys 0.45.0", 2105 - ] 2106 - 2107 - [[package]] 2108 - name = "jni-sys" 2109 - version = "0.3.0" 2110 - source = "registry+https://github.com/rust-lang/crates.io-index" 2111 - checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 2112 - 2113 - [[package]] 2114 - name = "jose-b64" 2115 - version = "0.1.2" 2116 - source = "registry+https://github.com/rust-lang/crates.io-index" 2117 - checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56" 2118 - dependencies = [ 2119 - "base64ct", 2120 - "serde", 2121 - "subtle", 2122 - "zeroize", 2123 - ] 2124 - 2125 - [[package]] 2126 - name = "jose-jwa" 2127 - version = "0.1.2" 2128 - source = "registry+https://github.com/rust-lang/crates.io-index" 2129 - checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7" 2130 - dependencies = [ 2131 - "serde", 2132 - ] 2133 - 2134 - [[package]] 2135 - name = "jose-jwk" 2136 - version = "0.1.2" 2137 - source = "registry+https://github.com/rust-lang/crates.io-index" 2138 - checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7" 2139 - dependencies = [ 2140 - "jose-b64", 2141 - "jose-jwa", 2142 - "p256", 2143 - "p384", 2144 - "rsa", 2145 - "serde", 2146 - "zeroize", 2147 - ] 2148 - 2149 - [[package]] 2150 - name = "js-sys" 2151 - version = "0.3.85" 2152 - source = "registry+https://github.com/rust-lang/crates.io-index" 2153 - checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" 2154 - dependencies = [ 2155 - "once_cell", 2156 - "wasm-bindgen", 2157 - ] 2158 - 2159 - [[package]] 2160 - name = "k256" 2161 - version = "0.13.4" 2162 - source = "registry+https://github.com/rust-lang/crates.io-index" 2163 - checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 2164 - dependencies = [ 2165 - "cfg-if", 2166 - "ecdsa", 2167 - "elliptic-curve", 2168 - "sha2", 2169 - ] 2170 - 2171 - [[package]] 2172 - name = "lazy_static" 2173 - version = "1.5.0" 2174 - source = "registry+https://github.com/rust-lang/crates.io-index" 2175 - checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 2176 - dependencies = [ 2177 - "spin 0.9.8", 2178 - ] 2179 - 2180 - [[package]] 2181 - name = "libc" 2182 - version = "0.2.180" 2183 - source = "registry+https://github.com/rust-lang/crates.io-index" 2184 - checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 2185 - 2186 - [[package]] 2187 - name = "libm" 2188 - version = "0.2.15" 2189 - source = "registry+https://github.com/rust-lang/crates.io-index" 2190 - checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 2191 - 2192 - [[package]] 2193 - name = "libredox" 2194 - version = "0.1.12" 2195 - source = "registry+https://github.com/rust-lang/crates.io-index" 2196 - checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" 2197 - dependencies = [ 2198 - "bitflags", 2199 - "libc", 2200 - "redox_syscall 0.7.0", 2201 - ] 2202 - 2203 - [[package]] 2204 - name = "linked-hash-map" 2205 - version = "0.5.6" 2206 - source = "registry+https://github.com/rust-lang/crates.io-index" 2207 - checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 2208 - 2209 - [[package]] 2210 - name = "linux-raw-sys" 2211 - version = "0.11.0" 2212 - source = "registry+https://github.com/rust-lang/crates.io-index" 2213 - checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 2214 - 2215 - [[package]] 2216 - name = "litemap" 2217 - version = "0.8.1" 2218 - source = "registry+https://github.com/rust-lang/crates.io-index" 2219 - checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 2220 - 2221 - [[package]] 2222 - name = "lock_api" 2223 - version = "0.4.14" 2224 - source = "registry+https://github.com/rust-lang/crates.io-index" 2225 - checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 2226 - dependencies = [ 2227 - "scopeguard", 2228 - ] 2229 - 2230 - [[package]] 2231 - name = "log" 2232 - version = "0.4.29" 2233 - source = "registry+https://github.com/rust-lang/crates.io-index" 2234 - checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 2235 - 2236 - [[package]] 2237 - name = "loom" 2238 - version = "0.7.2" 2239 - source = "registry+https://github.com/rust-lang/crates.io-index" 2240 - checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 2241 - dependencies = [ 2242 - "cfg-if", 2243 - "generator", 2244 - "scoped-tls", 2245 - "tracing", 2246 - "tracing-subscriber", 2247 - ] 2248 - 2249 - [[package]] 2250 - name = "lru-cache" 2251 - version = "0.1.2" 2252 - source = "registry+https://github.com/rust-lang/crates.io-index" 2253 - checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 2254 - dependencies = [ 2255 - "linked-hash-map", 2256 - ] 2257 - 2258 - [[package]] 2259 - name = "lru-slab" 2260 - version = "0.1.2" 2261 - source = "registry+https://github.com/rust-lang/crates.io-index" 2262 - checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 2263 - 2264 - [[package]] 2265 - name = "mac" 2266 - version = "0.1.1" 2267 - source = "registry+https://github.com/rust-lang/crates.io-index" 2268 - checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2269 - 2270 - [[package]] 2271 - name = "maitake-sync" 2272 - version = "0.1.2" 2273 - source = "registry+https://github.com/rust-lang/crates.io-index" 2274 - checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5" 2275 - dependencies = [ 2276 - "cordyceps", 2277 - "loom", 2278 - "mycelium-bitfield", 2279 - "pin-project", 2280 - "portable-atomic", 2281 - ] 2282 - 2283 - [[package]] 2284 - name = "markup5ever" 2285 - version = "0.12.1" 2286 - source = "registry+https://github.com/rust-lang/crates.io-index" 2287 - checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 2288 - dependencies = [ 2289 - "log", 2290 - "phf", 2291 - "phf_codegen", 2292 - "string_cache", 2293 - "string_cache_codegen", 2294 - "tendril", 2295 - ] 2296 - 2297 - [[package]] 2298 - name = "markup5ever_rcdom" 2299 - version = "0.3.0" 2300 - source = "registry+https://github.com/rust-lang/crates.io-index" 2301 - checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" 2302 - dependencies = [ 2303 - "html5ever", 2304 - "markup5ever", 2305 - "tendril", 2306 - "xml5ever", 2307 - ] 2308 - 2309 - [[package]] 2310 - name = "match-lookup" 2311 - version = "0.1.2" 2312 - source = "registry+https://github.com/rust-lang/crates.io-index" 2313 - checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 2314 - dependencies = [ 2315 - "proc-macro2", 2316 - "quote", 2317 - "syn", 2318 - ] 2319 - 2320 - [[package]] 2321 - name = "matchers" 2322 - version = "0.2.0" 2323 - source = "registry+https://github.com/rust-lang/crates.io-index" 2324 - checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 2325 - dependencies = [ 2326 - "regex-automata", 2327 - ] 2328 - 2329 - [[package]] 2330 - name = "matchit" 2331 - version = "0.8.4" 2332 - source = "registry+https://github.com/rust-lang/crates.io-index" 2333 - checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 2334 - 2335 - [[package]] 2336 - name = "memchr" 2337 - version = "2.7.6" 2338 - source = "registry+https://github.com/rust-lang/crates.io-index" 2339 - checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 2340 - 2341 - [[package]] 2342 - name = "miette" 2343 - version = "7.6.0" 2344 - source = "registry+https://github.com/rust-lang/crates.io-index" 2345 - checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 2346 - dependencies = [ 2347 - "backtrace", 2348 - "backtrace-ext", 2349 - "cfg-if", 2350 - "miette-derive", 2351 - "owo-colors", 2352 - "supports-color", 2353 - "supports-hyperlinks", 2354 - "supports-unicode", 2355 - "terminal_size", 2356 - "textwrap", 2357 - "unicode-width 0.1.14", 2358 - ] 2359 - 2360 - [[package]] 2361 - name = "miette-derive" 2362 - version = "7.6.0" 2363 - source = "registry+https://github.com/rust-lang/crates.io-index" 2364 - checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 2365 - dependencies = [ 2366 - "proc-macro2", 2367 - "quote", 2368 - "syn", 2369 - ] 2370 - 2371 - [[package]] 2372 - name = "mime" 2373 - version = "0.3.17" 2374 - source = "registry+https://github.com/rust-lang/crates.io-index" 2375 - checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 2376 - 2377 - [[package]] 2378 - name = "mime_guess" 2379 - version = "2.0.5" 2380 - source = "registry+https://github.com/rust-lang/crates.io-index" 2381 - checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 2382 - dependencies = [ 2383 - "mime", 2384 - "unicase", 2385 - ] 2386 - 2387 - [[package]] 2388 - name = "mini-moka-wasm" 2389 - version = "0.10.99" 2390 - source = "git+https://tangled.org/nonbinary.computer/jacquard#bfb72e29f20b0683e939db0140fa44cabde162d2" 2391 - dependencies = [ 2392 - "crossbeam-channel", 2393 - "crossbeam-utils", 2394 - "dashmap", 2395 - "smallvec", 2396 - "tagptr", 2397 - "triomphe", 2398 - "web-time", 2399 - ] 2400 - 2401 - [[package]] 2402 - name = "miniz_oxide" 2403 - version = "0.8.9" 2404 - source = "registry+https://github.com/rust-lang/crates.io-index" 2405 - checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 2406 - dependencies = [ 2407 - "adler2", 2408 - "simd-adler32", 2409 - ] 2410 - 2411 - [[package]] 2412 - name = "mio" 2413 - version = "1.1.1" 2414 - source = "registry+https://github.com/rust-lang/crates.io-index" 2415 - checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 2416 - dependencies = [ 2417 - "libc", 2418 - "wasi", 2419 - "windows-sys 0.61.2", 2420 - ] 2421 - 2422 - [[package]] 2423 - name = "multibase" 2424 - version = "0.9.2" 2425 - source = "registry+https://github.com/rust-lang/crates.io-index" 2426 - checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 2427 - dependencies = [ 2428 - "base-x", 2429 - "base256emoji", 2430 - "data-encoding", 2431 - "data-encoding-macro", 2432 - ] 2433 - 2434 - [[package]] 2435 - name = "multihash" 2436 - version = "0.19.3" 2437 - source = "registry+https://github.com/rust-lang/crates.io-index" 2438 - checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 2439 - dependencies = [ 2440 - "core2", 2441 - "serde", 2442 - "unsigned-varint", 2443 - ] 2444 - 2445 - [[package]] 2446 - name = "multipart" 2447 - version = "0.18.0" 2448 - source = "registry+https://github.com/rust-lang/crates.io-index" 2449 - checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" 2450 - dependencies = [ 2451 - "buf_redux", 2452 - "httparse", 2453 - "log", 2454 - "mime", 2455 - "mime_guess", 2456 - "quick-error", 2457 - "rand 0.8.5", 2458 - "safemem", 2459 - "tempfile", 2460 - "twoway", 2461 - ] 2462 - 2463 - [[package]] 2464 - name = "mycelium-bitfield" 2465 - version = "0.1.5" 2466 - source = "registry+https://github.com/rust-lang/crates.io-index" 2467 - checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 2468 - 2469 - [[package]] 2470 - name = "n0-future" 2471 - version = "0.1.3" 2472 - source = "registry+https://github.com/rust-lang/crates.io-index" 2473 - checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" 2474 - dependencies = [ 2475 - "cfg_aliases", 2476 - "derive_more 1.0.0", 2477 - "futures-buffered", 2478 - "futures-lite", 2479 - "futures-util", 2480 - "js-sys", 2481 - "pin-project", 2482 - "send_wrapper", 2483 - "tokio", 2484 - "tokio-util", 2485 - "wasm-bindgen", 2486 - "wasm-bindgen-futures", 2487 - "web-time", 2488 - ] 2489 - 2490 - [[package]] 2491 - name = "n0-future" 2492 - version = "0.3.2" 2493 - source = "registry+https://github.com/rust-lang/crates.io-index" 2494 - checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" 2495 - dependencies = [ 2496 - "cfg_aliases", 2497 - "derive_more 2.1.1", 2498 - "futures-buffered", 2499 - "futures-lite", 2500 - "futures-util", 2501 - "js-sys", 2502 - "pin-project", 2503 - "send_wrapper", 2504 - "tokio", 2505 - "tokio-util", 2506 - "wasm-bindgen", 2507 - "wasm-bindgen-futures", 2508 - "web-time", 2509 - ] 2510 - 2511 - [[package]] 2512 - name = "ndk-context" 2513 - version = "0.1.1" 2514 - source = "registry+https://github.com/rust-lang/crates.io-index" 2515 - checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 2516 - 2517 - [[package]] 2518 - name = "new_debug_unreachable" 2519 - version = "1.0.6" 2520 - source = "registry+https://github.com/rust-lang/crates.io-index" 2521 - checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 2522 - 2523 - [[package]] 2524 - name = "nu-ansi-term" 2525 - version = "0.50.3" 2526 - source = "registry+https://github.com/rust-lang/crates.io-index" 2527 - checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 2528 - dependencies = [ 2529 - "windows-sys 0.61.2", 2530 - ] 2531 - 2532 - [[package]] 2533 - name = "num-bigint-dig" 2534 - version = "0.8.6" 2535 - source = "registry+https://github.com/rust-lang/crates.io-index" 2536 - checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" 2537 - dependencies = [ 2538 - "lazy_static", 2539 - "libm", 2540 - "num-integer", 2541 - "num-iter", 2542 - "num-traits", 2543 - "rand 0.8.5", 2544 - "smallvec", 2545 - "zeroize", 2546 - ] 2547 - 2548 - [[package]] 2549 - name = "num-conv" 2550 - version = "0.1.0" 2551 - source = "registry+https://github.com/rust-lang/crates.io-index" 2552 - checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 2553 - 2554 - [[package]] 2555 - name = "num-integer" 2556 - version = "0.1.46" 2557 - source = "registry+https://github.com/rust-lang/crates.io-index" 2558 - checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 2559 - dependencies = [ 2560 - "num-traits", 2561 - ] 2562 - 2563 - [[package]] 2564 - name = "num-iter" 2565 - version = "0.1.45" 2566 - source = "registry+https://github.com/rust-lang/crates.io-index" 2567 - checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 2568 - dependencies = [ 2569 - "autocfg", 2570 - "num-integer", 2571 - "num-traits", 2572 - ] 2573 - 2574 - [[package]] 2575 - name = "num-traits" 2576 - version = "0.2.19" 2577 - source = "registry+https://github.com/rust-lang/crates.io-index" 2578 - checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 2579 - dependencies = [ 2580 - "autocfg", 2581 - "libm", 2582 - ] 2583 - 2584 - [[package]] 2585 - name = "num_cpus" 2586 - version = "1.17.0" 2587 - source = "registry+https://github.com/rust-lang/crates.io-index" 2588 - checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" 2589 - dependencies = [ 2590 - "hermit-abi", 2591 - "libc", 2592 - ] 2593 - 2594 - [[package]] 2595 - name = "num_threads" 2596 - version = "0.1.7" 2597 - source = "registry+https://github.com/rust-lang/crates.io-index" 2598 - checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 2599 - dependencies = [ 2600 - "libc", 2601 - ] 2602 - 2603 - [[package]] 2604 - name = "number_prefix" 2605 - version = "0.4.0" 2606 - source = "registry+https://github.com/rust-lang/crates.io-index" 2607 - checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 2608 - 2609 - [[package]] 2610 - name = "objc2" 2611 - version = "0.6.3" 2612 - source = "registry+https://github.com/rust-lang/crates.io-index" 2613 - checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" 2614 - dependencies = [ 2615 - "objc2-encode", 2616 - ] 2617 - 2618 - [[package]] 2619 - name = "objc2-encode" 2620 - version = "4.1.0" 2621 - source = "registry+https://github.com/rust-lang/crates.io-index" 2622 - checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 2623 - 2624 - [[package]] 2625 - name = "objc2-foundation" 2626 - version = "0.3.2" 2627 - source = "registry+https://github.com/rust-lang/crates.io-index" 2628 - checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 2629 - dependencies = [ 2630 - "bitflags", 2631 - "objc2", 2632 - ] 2633 - 2634 - [[package]] 2635 - name = "object" 2636 - version = "0.37.3" 2637 - source = "registry+https://github.com/rust-lang/crates.io-index" 2638 - checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" 2639 - dependencies = [ 2640 - "memchr", 2641 - ] 2642 - 2643 - [[package]] 2644 - name = "once_cell" 2645 - version = "1.21.3" 2646 - source = "registry+https://github.com/rust-lang/crates.io-index" 2647 - checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 2648 - 2649 - [[package]] 2650 - name = "once_cell_polyfill" 2651 - version = "1.70.2" 2652 - source = "registry+https://github.com/rust-lang/crates.io-index" 2653 - checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 2654 - 2655 - [[package]] 2656 - name = "openssl-probe" 2657 - version = "0.2.1" 2658 - source = "registry+https://github.com/rust-lang/crates.io-index" 2659 - checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 2660 - 2661 - [[package]] 2662 - name = "option-ext" 2663 - version = "0.2.0" 2664 - source = "registry+https://github.com/rust-lang/crates.io-index" 2665 - checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 2666 - 2667 - [[package]] 2668 - name = "ouroboros" 2669 - version = "0.18.5" 2670 - source = "registry+https://github.com/rust-lang/crates.io-index" 2671 - checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" 2672 - dependencies = [ 2673 - "aliasable", 2674 - "ouroboros_macro", 2675 - "static_assertions", 2676 - ] 2677 - 2678 - [[package]] 2679 - name = "ouroboros_macro" 2680 - version = "0.18.5" 2681 - source = "registry+https://github.com/rust-lang/crates.io-index" 2682 - checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" 2683 - dependencies = [ 2684 - "heck 0.4.1", 2685 - "proc-macro2", 2686 - "proc-macro2-diagnostics", 2687 - "quote", 2688 - "syn", 2689 - ] 2690 - 2691 - [[package]] 2692 - name = "owo-colors" 2693 - version = "4.2.3" 2694 - source = "registry+https://github.com/rust-lang/crates.io-index" 2695 - checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 2696 - 2697 - [[package]] 2698 - name = "oxilangtag" 2699 - version = "0.1.5" 2700 - source = "registry+https://github.com/rust-lang/crates.io-index" 2701 - checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb" 2702 - dependencies = [ 2703 - "serde", 2704 - ] 2705 - 2706 - [[package]] 2707 - name = "p256" 2708 - version = "0.13.2" 2709 - source = "registry+https://github.com/rust-lang/crates.io-index" 2710 - checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 2711 - dependencies = [ 2712 - "ecdsa", 2713 - "elliptic-curve", 2714 - "primeorder", 2715 - "sha2", 2716 - ] 2717 - 2718 - [[package]] 2719 - name = "p384" 2720 - version = "0.13.1" 2721 - source = "registry+https://github.com/rust-lang/crates.io-index" 2722 - checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 2723 - dependencies = [ 2724 - "elliptic-curve", 2725 - "primeorder", 2726 - ] 2727 - 2728 - [[package]] 2729 - name = "parking" 2730 - version = "2.2.1" 2731 - source = "registry+https://github.com/rust-lang/crates.io-index" 2732 - checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 2733 - 2734 - [[package]] 2735 - name = "parking_lot" 2736 - version = "0.12.5" 2737 - source = "registry+https://github.com/rust-lang/crates.io-index" 2738 - checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 2739 - dependencies = [ 2740 - "lock_api", 2741 - "parking_lot_core", 2742 - ] 2743 - 2744 - [[package]] 2745 - name = "parking_lot_core" 2746 - version = "0.9.12" 2747 - source = "registry+https://github.com/rust-lang/crates.io-index" 2748 - checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 2749 - dependencies = [ 2750 - "cfg-if", 2751 - "libc", 2752 - "redox_syscall 0.5.18", 2753 - "smallvec", 2754 - "windows-link", 2755 - ] 2756 - 2757 - [[package]] 2758 - name = "pem-rfc7468" 2759 - version = "0.7.0" 2760 - source = "registry+https://github.com/rust-lang/crates.io-index" 2761 - checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 2762 - dependencies = [ 2763 - "base64ct", 2764 - ] 2765 - 2766 - [[package]] 2767 - name = "percent-encoding" 2768 - version = "2.3.2" 2769 - source = "registry+https://github.com/rust-lang/crates.io-index" 2770 - checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2771 - 2772 - [[package]] 2773 - name = "phf" 2774 - version = "0.11.3" 2775 - source = "registry+https://github.com/rust-lang/crates.io-index" 2776 - checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 2777 - dependencies = [ 2778 - "phf_shared", 2779 - ] 2780 - 2781 - [[package]] 2782 - name = "phf_codegen" 2783 - version = "0.11.3" 2784 - source = "registry+https://github.com/rust-lang/crates.io-index" 2785 - checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" 2786 - dependencies = [ 2787 - "phf_generator", 2788 - "phf_shared", 2789 - ] 2790 - 2791 - [[package]] 2792 - name = "phf_generator" 2793 - version = "0.11.3" 2794 - source = "registry+https://github.com/rust-lang/crates.io-index" 2795 - checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 2796 - dependencies = [ 2797 - "phf_shared", 2798 - "rand 0.8.5", 2799 - ] 2800 - 2801 - [[package]] 2802 - name = "phf_shared" 2803 - version = "0.11.3" 2804 - source = "registry+https://github.com/rust-lang/crates.io-index" 2805 - checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 2806 - dependencies = [ 2807 - "siphasher", 2808 - ] 2809 - 2810 - [[package]] 2811 - name = "pin-project" 2812 - version = "1.1.10" 2813 - source = "registry+https://github.com/rust-lang/crates.io-index" 2814 - checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 2815 - dependencies = [ 2816 - "pin-project-internal", 2817 - ] 2818 - 2819 - [[package]] 2820 - name = "pin-project-internal" 2821 - version = "1.1.10" 2822 - source = "registry+https://github.com/rust-lang/crates.io-index" 2823 - checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 2824 - dependencies = [ 2825 - "proc-macro2", 2826 - "quote", 2827 - "syn", 2828 - ] 2829 - 2830 - [[package]] 2831 - name = "pin-project-lite" 2832 - version = "0.2.16" 2833 - source = "registry+https://github.com/rust-lang/crates.io-index" 2834 - checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 2835 - 2836 - [[package]] 2837 - name = "pin-utils" 2838 - version = "0.1.0" 2839 - source = "registry+https://github.com/rust-lang/crates.io-index" 2840 - checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2841 - 2842 - [[package]] 2843 - name = "pkcs1" 2844 - version = "0.7.5" 2845 - source = "registry+https://github.com/rust-lang/crates.io-index" 2846 - checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 2847 - dependencies = [ 2848 - "der", 2849 - "pkcs8", 2850 - "spki", 2851 - ] 2852 - 2853 - [[package]] 2854 - name = "pkcs8" 2855 - version = "0.10.2" 2856 - source = "registry+https://github.com/rust-lang/crates.io-index" 2857 - checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 2858 - dependencies = [ 2859 - "der", 2860 - "spki", 2861 - ] 2862 - 2863 - [[package]] 2864 - name = "portable-atomic" 2865 - version = "1.13.0" 2866 - source = "registry+https://github.com/rust-lang/crates.io-index" 2867 - checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" 2868 - 2869 - [[package]] 2870 - name = "postcard" 2871 - version = "1.1.3" 2872 - source = "registry+https://github.com/rust-lang/crates.io-index" 2873 - checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" 2874 - dependencies = [ 2875 - "cobs", 2876 - "embedded-io 0.4.0", 2877 - "embedded-io 0.6.1", 2878 - "heapless", 2879 - "serde", 2880 - ] 2881 - 2882 - [[package]] 2883 - name = "potential_utf" 2884 - version = "0.1.4" 2885 - source = "registry+https://github.com/rust-lang/crates.io-index" 2886 - checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 2887 - dependencies = [ 2888 - "zerovec", 2889 - ] 2890 - 2891 - [[package]] 2892 - name = "powerfmt" 2893 - version = "0.2.0" 2894 - source = "registry+https://github.com/rust-lang/crates.io-index" 2895 - checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2896 - 2897 - [[package]] 2898 - name = "ppv-lite86" 2899 - version = "0.2.21" 2900 - source = "registry+https://github.com/rust-lang/crates.io-index" 2901 - checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2902 - dependencies = [ 2903 - "zerocopy", 2904 - ] 2905 - 2906 - [[package]] 2907 - name = "precomputed-hash" 2908 - version = "0.1.1" 2909 - source = "registry+https://github.com/rust-lang/crates.io-index" 2910 - checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 2911 - 2912 - [[package]] 2913 - name = "prettyplease" 2914 - version = "0.2.37" 2915 - source = "registry+https://github.com/rust-lang/crates.io-index" 2916 - checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2917 - dependencies = [ 2918 - "proc-macro2", 2919 - "syn", 2920 - ] 2921 - 2922 - [[package]] 2923 - name = "primeorder" 2924 - version = "0.13.6" 2925 - source = "registry+https://github.com/rust-lang/crates.io-index" 2926 - checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2927 - dependencies = [ 2928 - "elliptic-curve", 2929 - ] 2930 - 2931 - [[package]] 2932 - name = "proc-macro2" 2933 - version = "1.0.106" 2934 - source = "registry+https://github.com/rust-lang/crates.io-index" 2935 - checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 2936 - dependencies = [ 2937 - "unicode-ident", 2938 - ] 2939 - 2940 - [[package]] 2941 - name = "proc-macro2-diagnostics" 2942 - version = "0.10.1" 2943 - source = "registry+https://github.com/rust-lang/crates.io-index" 2944 - checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 2945 - dependencies = [ 2946 - "proc-macro2", 2947 - "quote", 2948 - "syn", 2949 - "version_check", 2950 - "yansi", 2951 - ] 2952 - 2953 - [[package]] 2954 - name = "quick-error" 2955 - version = "1.2.3" 2956 - source = "registry+https://github.com/rust-lang/crates.io-index" 2957 - checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 2958 - 2959 - [[package]] 2960 - name = "quinn" 2961 - version = "0.11.9" 2962 - source = "registry+https://github.com/rust-lang/crates.io-index" 2963 - checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 2964 - dependencies = [ 2965 - "bytes", 2966 - "cfg_aliases", 2967 - "pin-project-lite", 2968 - "quinn-proto", 2969 - "quinn-udp", 2970 - "rustc-hash", 2971 - "rustls", 2972 - "socket2 0.6.2", 2973 - "thiserror 2.0.18", 2974 - "tokio", 2975 - "tracing", 2976 - "web-time", 2977 - ] 2978 - 2979 - [[package]] 2980 - name = "quinn-proto" 2981 - version = "0.11.13" 2982 - source = "registry+https://github.com/rust-lang/crates.io-index" 2983 - checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2984 - dependencies = [ 2985 - "bytes", 2986 - "getrandom 0.3.4", 2987 - "lru-slab", 2988 - "rand 0.9.2", 2989 - "ring", 2990 - "rustc-hash", 2991 - "rustls", 2992 - "rustls-pki-types", 2993 - "slab", 2994 - "thiserror 2.0.18", 2995 - "tinyvec", 2996 - "tracing", 2997 - "web-time", 2998 - ] 2999 - 3000 - [[package]] 3001 - name = "quinn-udp" 3002 - version = "0.5.14" 3003 - source = "registry+https://github.com/rust-lang/crates.io-index" 3004 - checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 3005 - dependencies = [ 3006 - "cfg_aliases", 3007 - "libc", 3008 - "once_cell", 3009 - "socket2 0.6.2", 3010 - "tracing", 3011 - "windows-sys 0.60.2", 3012 - ] 3013 - 3014 - [[package]] 3015 - name = "quote" 3016 - version = "1.0.44" 3017 - source = "registry+https://github.com/rust-lang/crates.io-index" 3018 - checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 3019 - dependencies = [ 3020 - "proc-macro2", 3021 - ] 3022 - 3023 - [[package]] 3024 - name = "r-efi" 3025 - version = "5.3.0" 3026 - source = "registry+https://github.com/rust-lang/crates.io-index" 3027 - checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 3028 - 3029 - [[package]] 3030 - name = "rand" 3031 - version = "0.8.5" 3032 - source = "registry+https://github.com/rust-lang/crates.io-index" 3033 - checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 3034 - dependencies = [ 3035 - "libc", 3036 - "rand_chacha 0.3.1", 3037 - "rand_core 0.6.4", 3038 - ] 3039 - 3040 - [[package]] 3041 - name = "rand" 3042 - version = "0.9.2" 3043 - source = "registry+https://github.com/rust-lang/crates.io-index" 3044 - checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 3045 - dependencies = [ 3046 - "rand_chacha 0.9.0", 3047 - "rand_core 0.9.5", 3048 - ] 3049 - 3050 - [[package]] 3051 - name = "rand_chacha" 3052 - version = "0.3.1" 3053 - source = "registry+https://github.com/rust-lang/crates.io-index" 3054 - checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 3055 - dependencies = [ 3056 - "ppv-lite86", 3057 - "rand_core 0.6.4", 3058 - ] 3059 - 3060 - [[package]] 3061 - name = "rand_chacha" 3062 - version = "0.9.0" 3063 - source = "registry+https://github.com/rust-lang/crates.io-index" 3064 - checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 3065 - dependencies = [ 3066 - "ppv-lite86", 3067 - "rand_core 0.9.5", 3068 - ] 3069 - 3070 - [[package]] 3071 - name = "rand_core" 3072 - version = "0.6.4" 3073 - source = "registry+https://github.com/rust-lang/crates.io-index" 3074 - checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 3075 - dependencies = [ 3076 - "getrandom 0.2.17", 3077 - ] 3078 - 3079 - [[package]] 3080 - name = "rand_core" 3081 - version = "0.9.5" 3082 - source = "registry+https://github.com/rust-lang/crates.io-index" 3083 - checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 3084 - dependencies = [ 3085 - "getrandom 0.3.4", 3086 - ] 3087 - 3088 - [[package]] 3089 - name = "redox_syscall" 3090 - version = "0.5.18" 3091 - source = "registry+https://github.com/rust-lang/crates.io-index" 3092 - checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3093 - dependencies = [ 3094 - "bitflags", 3095 - ] 3096 - 3097 - [[package]] 3098 - name = "redox_syscall" 3099 - version = "0.7.0" 3100 - source = "registry+https://github.com/rust-lang/crates.io-index" 3101 - checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" 3102 - dependencies = [ 3103 - "bitflags", 3104 - ] 3105 - 3106 - [[package]] 3107 - name = "redox_users" 3108 - version = "0.5.2" 3109 - source = "registry+https://github.com/rust-lang/crates.io-index" 3110 - checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" 3111 - dependencies = [ 3112 - "getrandom 0.2.17", 3113 - "libredox", 3114 - "thiserror 2.0.18", 3115 - ] 3116 - 3117 - [[package]] 3118 - name = "regex" 3119 - version = "1.12.2" 3120 - source = "registry+https://github.com/rust-lang/crates.io-index" 3121 - checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 3122 - dependencies = [ 3123 - "aho-corasick", 3124 - "memchr", 3125 - "regex-automata", 3126 - "regex-syntax", 3127 - ] 3128 - 3129 - [[package]] 3130 - name = "regex-automata" 3131 - version = "0.4.13" 3132 - source = "registry+https://github.com/rust-lang/crates.io-index" 3133 - checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 3134 - dependencies = [ 3135 - "aho-corasick", 3136 - "memchr", 3137 - "regex-syntax", 3138 - ] 3139 - 3140 - [[package]] 3141 - name = "regex-lite" 3142 - version = "0.1.8" 3143 - source = "registry+https://github.com/rust-lang/crates.io-index" 3144 - checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 3145 - 3146 - [[package]] 3147 - name = "regex-syntax" 3148 - version = "0.8.8" 3149 - source = "registry+https://github.com/rust-lang/crates.io-index" 3150 - checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 3151 - 3152 - [[package]] 3153 - name = "reqwest" 3154 - version = "0.12.28" 3155 - source = "registry+https://github.com/rust-lang/crates.io-index" 3156 - checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 3157 - dependencies = [ 3158 - "base64 0.22.1", 3159 - "bytes", 3160 - "encoding_rs", 3161 - "futures-core", 3162 - "futures-util", 3163 - "h2", 3164 - "http", 3165 - "http-body", 3166 - "http-body-util", 3167 - "hyper", 3168 - "hyper-rustls", 3169 - "hyper-util", 3170 - "js-sys", 3171 - "log", 3172 - "mime", 3173 - "percent-encoding", 3174 - "pin-project-lite", 3175 - "quinn", 3176 - "rustls", 3177 - "rustls-pki-types", 3178 - "serde", 3179 - "serde_json", 3180 - "serde_urlencoded", 3181 - "sync_wrapper", 3182 - "tokio", 3183 - "tokio-rustls", 3184 - "tokio-util", 3185 - "tower", 3186 - "tower-http", 3187 - "tower-service", 3188 - "url", 3189 - "wasm-bindgen", 3190 - "wasm-bindgen-futures", 3191 - "wasm-streams", 3192 - "web-sys", 3193 - "webpki-roots", 3194 - ] 3195 - 3196 - [[package]] 3197 - name = "resolv-conf" 3198 - version = "0.7.6" 3199 - source = "registry+https://github.com/rust-lang/crates.io-index" 3200 - checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 3201 - 3202 - [[package]] 3203 - name = "rfc6979" 3204 - version = "0.4.0" 3205 - source = "registry+https://github.com/rust-lang/crates.io-index" 3206 - checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 3207 - dependencies = [ 3208 - "hmac", 3209 - "subtle", 3210 - ] 3211 - 3212 - [[package]] 3213 - name = "ring" 3214 - version = "0.17.14" 3215 - source = "registry+https://github.com/rust-lang/crates.io-index" 3216 - checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 3217 - dependencies = [ 3218 - "cc", 3219 - "cfg-if", 3220 - "getrandom 0.2.17", 3221 - "libc", 3222 - "untrusted", 3223 - "windows-sys 0.52.0", 3224 - ] 3225 - 3226 - [[package]] 3227 - name = "rouille" 3228 - version = "3.6.2" 3229 - source = "registry+https://github.com/rust-lang/crates.io-index" 3230 - checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" 3231 - dependencies = [ 3232 - "base64 0.13.1", 3233 - "brotli", 3234 - "chrono", 3235 - "deflate", 3236 - "filetime", 3237 - "multipart", 3238 - "percent-encoding", 3239 - "rand 0.8.5", 3240 - "serde", 3241 - "serde_derive", 3242 - "serde_json", 3243 - "sha1_smol", 3244 - "threadpool", 3245 - "time", 3246 - "tiny_http", 3247 - "url", 3248 - ] 3249 - 3250 - [[package]] 3251 - name = "rsa" 3252 - version = "0.9.10" 3253 - source = "registry+https://github.com/rust-lang/crates.io-index" 3254 - checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" 3255 - dependencies = [ 3256 - "const-oid", 3257 - "digest", 3258 - "num-bigint-dig", 3259 - "num-integer", 3260 - "num-traits", 3261 - "pkcs1", 3262 - "pkcs8", 3263 - "rand_core 0.6.4", 3264 - "signature", 3265 - "spki", 3266 - "subtle", 3267 - "zeroize", 3268 - ] 3269 - 3270 - [[package]] 3271 - name = "rustc-demangle" 3272 - version = "0.1.27" 3273 - source = "registry+https://github.com/rust-lang/crates.io-index" 3274 - checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" 3275 - 3276 - [[package]] 3277 - name = "rustc-hash" 3278 - version = "2.1.1" 3279 - source = "registry+https://github.com/rust-lang/crates.io-index" 3280 - checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 3281 - 3282 - [[package]] 3283 - name = "rustc_version" 3284 - version = "0.4.1" 3285 - source = "registry+https://github.com/rust-lang/crates.io-index" 3286 - checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 3287 - dependencies = [ 3288 - "semver", 3289 - ] 3290 - 3291 - [[package]] 3292 - name = "rustix" 3293 - version = "1.1.3" 3294 - source = "registry+https://github.com/rust-lang/crates.io-index" 3295 - checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3296 - dependencies = [ 3297 - "bitflags", 3298 - "errno", 3299 - "libc", 3300 - "linux-raw-sys", 3301 - "windows-sys 0.61.2", 3302 - ] 3303 - 3304 - [[package]] 3305 - name = "rustls" 3306 - version = "0.23.36" 3307 - source = "registry+https://github.com/rust-lang/crates.io-index" 3308 - checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 3309 - dependencies = [ 3310 - "once_cell", 3311 - "ring", 3312 - "rustls-pki-types", 3313 - "rustls-webpki", 3314 - "subtle", 3315 - "zeroize", 3316 - ] 3317 - 3318 - [[package]] 3319 - name = "rustls-native-certs" 3320 - version = "0.8.3" 3321 - source = "registry+https://github.com/rust-lang/crates.io-index" 3322 - checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 3323 - dependencies = [ 3324 - "openssl-probe", 3325 - "rustls-pki-types", 3326 - "schannel", 3327 - "security-framework", 3328 - ] 3329 - 3330 - [[package]] 3331 - name = "rustls-pki-types" 3332 - version = "1.14.0" 3333 - source = "registry+https://github.com/rust-lang/crates.io-index" 3334 - checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" 3335 - dependencies = [ 3336 - "web-time", 3337 - "zeroize", 3338 - ] 3339 - 3340 - [[package]] 3341 - name = "rustls-webpki" 3342 - version = "0.103.9" 3343 - source = "registry+https://github.com/rust-lang/crates.io-index" 3344 - checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" 3345 - dependencies = [ 3346 - "ring", 3347 - "rustls-pki-types", 3348 - "untrusted", 3349 - ] 3350 - 3351 - [[package]] 3352 - name = "rustversion" 3353 - version = "1.0.22" 3354 - source = "registry+https://github.com/rust-lang/crates.io-index" 3355 - checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 3356 - 3357 - [[package]] 3358 - name = "ryu" 3359 - version = "1.0.22" 3360 - source = "registry+https://github.com/rust-lang/crates.io-index" 3361 - checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 3362 - 3363 - [[package]] 3364 - name = "safemem" 3365 - version = "0.3.3" 3366 - source = "registry+https://github.com/rust-lang/crates.io-index" 3367 - checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 3368 - 3369 - [[package]] 3370 - name = "same-file" 3371 - version = "1.0.6" 3372 - source = "registry+https://github.com/rust-lang/crates.io-index" 3373 - checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 3374 - dependencies = [ 3375 - "winapi-util", 3376 - ] 3377 - 3378 - [[package]] 3379 - name = "schannel" 3380 - version = "0.1.28" 3381 - source = "registry+https://github.com/rust-lang/crates.io-index" 3382 - checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 3383 - dependencies = [ 3384 - "windows-sys 0.61.2", 3385 - ] 3386 - 3387 - [[package]] 3388 - name = "scoped-tls" 3389 - version = "1.0.1" 3390 - source = "registry+https://github.com/rust-lang/crates.io-index" 3391 - checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 3392 - 3393 - [[package]] 3394 - name = "scopeguard" 3395 - version = "1.2.0" 3396 - source = "registry+https://github.com/rust-lang/crates.io-index" 3397 - checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 3398 - 3399 - [[package]] 3400 - name = "sec1" 3401 - version = "0.7.3" 3402 - source = "registry+https://github.com/rust-lang/crates.io-index" 3403 - checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 3404 - dependencies = [ 3405 - "base16ct", 3406 - "der", 3407 - "generic-array", 3408 - "pkcs8", 3409 - "subtle", 3410 - "zeroize", 3411 - ] 3412 - 3413 - [[package]] 3414 - name = "security-framework" 3415 - version = "3.5.1" 3416 - source = "registry+https://github.com/rust-lang/crates.io-index" 3417 - checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 3418 - dependencies = [ 3419 - "bitflags", 3420 - "core-foundation 0.10.1", 3421 - "core-foundation-sys", 3422 - "libc", 3423 - "security-framework-sys", 3424 - ] 3425 - 3426 - [[package]] 3427 - name = "security-framework-sys" 3428 - version = "2.15.0" 3429 - source = "registry+https://github.com/rust-lang/crates.io-index" 3430 - checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 3431 - dependencies = [ 3432 - "core-foundation-sys", 3433 - "libc", 3434 - ] 3435 - 3436 - [[package]] 3437 - name = "semver" 3438 - version = "1.0.27" 3439 - source = "registry+https://github.com/rust-lang/crates.io-index" 3440 - checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 3441 - 3442 - [[package]] 3443 - name = "send_wrapper" 3444 - version = "0.6.0" 3445 - source = "registry+https://github.com/rust-lang/crates.io-index" 3446 - checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 3447 - 3448 - [[package]] 3449 - name = "serde" 3450 - version = "1.0.228" 3451 - source = "registry+https://github.com/rust-lang/crates.io-index" 3452 - checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 3453 - dependencies = [ 3454 - "serde_core", 3455 - "serde_derive", 3456 - ] 3457 - 3458 - [[package]] 3459 - name = "serde_bytes" 3460 - version = "0.11.19" 3461 - source = "registry+https://github.com/rust-lang/crates.io-index" 3462 - checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 3463 - dependencies = [ 3464 - "serde", 3465 - "serde_core", 3466 - ] 3467 - 3468 - [[package]] 3469 - name = "serde_core" 3470 - version = "1.0.228" 3471 - source = "registry+https://github.com/rust-lang/crates.io-index" 3472 - checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 3473 - dependencies = [ 3474 - "serde_derive", 3475 - ] 3476 - 3477 - [[package]] 3478 - name = "serde_derive" 3479 - version = "1.0.228" 3480 - source = "registry+https://github.com/rust-lang/crates.io-index" 3481 - checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 3482 - dependencies = [ 3483 - "proc-macro2", 3484 - "quote", 3485 - "syn", 3486 - ] 3487 - 3488 - [[package]] 3489 - name = "serde_html_form" 3490 - version = "0.3.2" 3491 - source = "registry+https://github.com/rust-lang/crates.io-index" 3492 - checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" 3493 - dependencies = [ 3494 - "form_urlencoded", 3495 - "indexmap", 3496 - "itoa", 3497 - "serde_core", 3498 - ] 3499 - 3500 - [[package]] 3501 - name = "serde_ipld_dagcbor" 3502 - version = "0.6.4" 3503 - source = "registry+https://github.com/rust-lang/crates.io-index" 3504 - checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 3505 - dependencies = [ 3506 - "cbor4ii", 3507 - "ipld-core", 3508 - "scopeguard", 3509 - "serde", 3510 - ] 3511 - 3512 - [[package]] 3513 - name = "serde_json" 3514 - version = "1.0.149" 3515 - source = "registry+https://github.com/rust-lang/crates.io-index" 3516 - checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 3517 - dependencies = [ 3518 - "itoa", 3519 - "memchr", 3520 - "serde", 3521 - "serde_core", 3522 - "zmij", 3523 - ] 3524 - 3525 - [[package]] 3526 - name = "serde_path_to_error" 3527 - version = "0.1.20" 3528 - source = "registry+https://github.com/rust-lang/crates.io-index" 3529 - checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 3530 - dependencies = [ 3531 - "itoa", 3532 - "serde", 3533 - "serde_core", 3534 - ] 3535 - 3536 - [[package]] 3537 - name = "serde_repr" 3538 - version = "0.1.20" 3539 - source = "registry+https://github.com/rust-lang/crates.io-index" 3540 - checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 3541 - dependencies = [ 3542 - "proc-macro2", 3543 - "quote", 3544 - "syn", 3545 - ] 3546 - 3547 - [[package]] 3548 - name = "serde_urlencoded" 3549 - version = "0.7.1" 3550 - source = "registry+https://github.com/rust-lang/crates.io-index" 3551 - checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 3552 - dependencies = [ 3553 - "form_urlencoded", 3554 - "itoa", 3555 - "ryu", 3556 - "serde", 3557 - ] 3558 - 3559 - [[package]] 3560 - name = "serde_with" 3561 - version = "3.16.1" 3562 - source = "registry+https://github.com/rust-lang/crates.io-index" 3563 - checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" 3564 - dependencies = [ 3565 - "base64 0.22.1", 3566 - "chrono", 3567 - "hex", 3568 - "serde_core", 3569 - "serde_json", 3570 - "serde_with_macros", 3571 - "time", 3572 - ] 3573 - 3574 - [[package]] 3575 - name = "serde_with_macros" 3576 - version = "3.16.1" 3577 - source = "registry+https://github.com/rust-lang/crates.io-index" 3578 - checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" 3579 - dependencies = [ 3580 - "darling 0.21.3", 3581 - "proc-macro2", 3582 - "quote", 3583 - "syn", 3584 - ] 3585 - 3586 - [[package]] 3587 - name = "sha1" 3588 - version = "0.10.6" 3589 - source = "registry+https://github.com/rust-lang/crates.io-index" 3590 - checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 3591 - dependencies = [ 3592 - "cfg-if", 3593 - "cpufeatures", 3594 - "digest", 3595 - ] 3596 - 3597 - [[package]] 3598 - name = "sha1_smol" 3599 - version = "1.0.1" 3600 - source = "registry+https://github.com/rust-lang/crates.io-index" 3601 - checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 3602 - 3603 - [[package]] 3604 - name = "sha2" 3605 - version = "0.10.9" 3606 - source = "registry+https://github.com/rust-lang/crates.io-index" 3607 - checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 3608 - dependencies = [ 3609 - "cfg-if", 3610 - "cpufeatures", 3611 - "digest", 3612 - ] 3613 - 3614 - [[package]] 3615 - name = "sharded-slab" 3616 - version = "0.1.7" 3617 - source = "registry+https://github.com/rust-lang/crates.io-index" 3618 - checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 3619 - dependencies = [ 3620 - "lazy_static", 3621 - ] 3622 - 3623 - [[package]] 3624 - name = "shellexpand" 3625 - version = "3.1.1" 3626 - source = "registry+https://github.com/rust-lang/crates.io-index" 3627 - checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" 3628 - dependencies = [ 3629 - "dirs", 3630 - ] 3631 - 3632 - [[package]] 3633 - name = "shlex" 3634 - version = "1.3.0" 3635 - source = "registry+https://github.com/rust-lang/crates.io-index" 3636 - checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 3637 - 3638 - [[package]] 3639 - name = "signal-hook-registry" 3640 - version = "1.4.8" 3641 - source = "registry+https://github.com/rust-lang/crates.io-index" 3642 - checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 3643 - dependencies = [ 3644 - "errno", 3645 - "libc", 3646 - ] 3647 - 3648 - [[package]] 3649 - name = "signature" 3650 - version = "2.2.0" 3651 - source = "registry+https://github.com/rust-lang/crates.io-index" 3652 - checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 3653 - dependencies = [ 3654 - "digest", 3655 - "rand_core 0.6.4", 3656 - ] 3657 - 3658 - [[package]] 3659 - name = "simd-adler32" 3660 - version = "0.3.8" 3661 - source = "registry+https://github.com/rust-lang/crates.io-index" 3662 - checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 3663 - 3664 - [[package]] 3665 - name = "siphasher" 3666 - version = "1.0.1" 3667 - source = "registry+https://github.com/rust-lang/crates.io-index" 3668 - checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 3669 - 3670 - [[package]] 3671 - name = "slab" 3672 - version = "0.4.11" 3673 - source = "registry+https://github.com/rust-lang/crates.io-index" 3674 - checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 3675 - 3676 - [[package]] 3677 - name = "smallvec" 3678 - version = "1.15.1" 3679 - source = "registry+https://github.com/rust-lang/crates.io-index" 3680 - checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 3681 - 3682 - [[package]] 3683 - name = "smol_str" 3684 - version = "0.3.5" 3685 - source = "registry+https://github.com/rust-lang/crates.io-index" 3686 - checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" 3687 - dependencies = [ 3688 - "borsh", 3689 - "serde_core", 3690 - ] 3691 - 3692 - [[package]] 3693 - name = "socket2" 3694 - version = "0.5.10" 3695 - source = "registry+https://github.com/rust-lang/crates.io-index" 3696 - checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 3697 - dependencies = [ 3698 - "libc", 3699 - "windows-sys 0.52.0", 3700 - ] 3701 - 3702 - [[package]] 3703 - name = "socket2" 3704 - version = "0.6.2" 3705 - source = "registry+https://github.com/rust-lang/crates.io-index" 3706 - checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" 3707 - dependencies = [ 3708 - "libc", 3709 - "windows-sys 0.60.2", 3710 - ] 3711 - 3712 - [[package]] 3713 - name = "spin" 3714 - version = "0.9.8" 3715 - source = "registry+https://github.com/rust-lang/crates.io-index" 3716 - checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 3717 - dependencies = [ 3718 - "lock_api", 3719 - ] 3720 - 3721 - [[package]] 3722 - name = "spin" 3723 - version = "0.10.0" 3724 - source = "registry+https://github.com/rust-lang/crates.io-index" 3725 - checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 3726 - 3727 - [[package]] 3728 - name = "spki" 3729 - version = "0.7.3" 3730 - source = "registry+https://github.com/rust-lang/crates.io-index" 3731 - checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 3732 - dependencies = [ 3733 - "base64ct", 3734 - "der", 3735 - ] 3736 - 3737 - [[package]] 3738 - name = "stable_deref_trait" 3739 - version = "1.2.1" 3740 - source = "registry+https://github.com/rust-lang/crates.io-index" 3741 - checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 3742 - 3743 - [[package]] 3744 - name = "static_assertions" 3745 - version = "1.1.0" 3746 - source = "registry+https://github.com/rust-lang/crates.io-index" 3747 - checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 3748 - 3749 - [[package]] 3750 - name = "string_cache" 3751 - version = "0.8.9" 3752 - source = "registry+https://github.com/rust-lang/crates.io-index" 3753 - checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 3754 - dependencies = [ 3755 - "new_debug_unreachable", 3756 - "parking_lot", 3757 - "phf_shared", 3758 - "precomputed-hash", 3759 - "serde", 3760 - ] 3761 - 3762 - [[package]] 3763 - name = "string_cache_codegen" 3764 - version = "0.5.4" 3765 - source = "registry+https://github.com/rust-lang/crates.io-index" 3766 - checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" 3767 - dependencies = [ 3768 - "phf_generator", 3769 - "phf_shared", 3770 - "proc-macro2", 3771 - "quote", 3772 - ] 3773 - 3774 - [[package]] 3775 - name = "strsim" 3776 - version = "0.11.1" 3777 - source = "registry+https://github.com/rust-lang/crates.io-index" 3778 - checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3779 - 3780 - [[package]] 3781 - name = "subtle" 3782 - version = "2.6.1" 3783 - source = "registry+https://github.com/rust-lang/crates.io-index" 3784 - checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 3785 - 3786 - [[package]] 3787 - name = "supports-color" 3788 - version = "3.0.2" 3789 - source = "registry+https://github.com/rust-lang/crates.io-index" 3790 - checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" 3791 - dependencies = [ 3792 - "is_ci", 3793 - ] 3794 - 3795 - [[package]] 3796 - name = "supports-hyperlinks" 3797 - version = "3.2.0" 3798 - source = "registry+https://github.com/rust-lang/crates.io-index" 3799 - checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" 3800 - 3801 - [[package]] 3802 - name = "supports-unicode" 3803 - version = "3.0.0" 3804 - source = "registry+https://github.com/rust-lang/crates.io-index" 3805 - checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" 3806 - 3807 - [[package]] 3808 - name = "syn" 3809 - version = "2.0.114" 3810 - source = "registry+https://github.com/rust-lang/crates.io-index" 3811 - checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 3812 - dependencies = [ 3813 - "proc-macro2", 3814 - "quote", 3815 - "unicode-ident", 3816 - ] 3817 - 3818 - [[package]] 3819 - name = "sync_wrapper" 3820 - version = "1.0.2" 3821 - source = "registry+https://github.com/rust-lang/crates.io-index" 3822 - checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3823 - dependencies = [ 3824 - "futures-core", 3825 - ] 3826 - 3827 - [[package]] 3828 - name = "synstructure" 3829 - version = "0.13.2" 3830 - source = "registry+https://github.com/rust-lang/crates.io-index" 3831 - checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 3832 - dependencies = [ 3833 - "proc-macro2", 3834 - "quote", 3835 - "syn", 3836 - ] 3837 - 3838 - [[package]] 3839 - name = "system-configuration" 3840 - version = "0.6.1" 3841 - source = "registry+https://github.com/rust-lang/crates.io-index" 3842 - checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 3843 - dependencies = [ 3844 - "bitflags", 3845 - "core-foundation 0.9.4", 3846 - "system-configuration-sys", 3847 - ] 3848 - 3849 - [[package]] 3850 - name = "system-configuration-sys" 3851 - version = "0.6.0" 3852 - source = "registry+https://github.com/rust-lang/crates.io-index" 3853 - checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3854 - dependencies = [ 3855 - "core-foundation-sys", 3856 - "libc", 3857 - ] 3858 - 3859 - [[package]] 3860 - name = "tagptr" 3861 - version = "0.2.0" 3862 - source = "registry+https://github.com/rust-lang/crates.io-index" 3863 - checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 3864 - 3865 - [[package]] 3866 - name = "tempfile" 3867 - version = "3.24.0" 3868 - source = "registry+https://github.com/rust-lang/crates.io-index" 3869 - checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 3870 - dependencies = [ 3871 - "fastrand", 3872 - "getrandom 0.3.4", 3873 - "once_cell", 3874 - "rustix", 3875 - "windows-sys 0.61.2", 3876 - ] 3877 - 3878 - [[package]] 3879 - name = "tendril" 3880 - version = "0.4.3" 3881 - source = "registry+https://github.com/rust-lang/crates.io-index" 3882 - checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 3883 - dependencies = [ 3884 - "futf", 3885 - "mac", 3886 - "utf-8", 3887 - ] 3888 - 3889 - [[package]] 3890 - name = "terminal_size" 3891 - version = "0.4.3" 3892 - source = "registry+https://github.com/rust-lang/crates.io-index" 3893 - checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" 3894 - dependencies = [ 3895 - "rustix", 3896 - "windows-sys 0.60.2", 3897 - ] 3898 - 3899 - [[package]] 3900 - name = "textwrap" 3901 - version = "0.16.2" 3902 - source = "registry+https://github.com/rust-lang/crates.io-index" 3903 - checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" 3904 - dependencies = [ 3905 - "unicode-linebreak", 3906 - "unicode-width 0.2.2", 3907 - ] 3908 - 3909 - [[package]] 3910 - name = "thiserror" 3911 - version = "1.0.69" 3912 - source = "registry+https://github.com/rust-lang/crates.io-index" 3913 - checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 3914 - dependencies = [ 3915 - "thiserror-impl 1.0.69", 3916 - ] 3917 - 3918 - [[package]] 3919 - name = "thiserror" 3920 - version = "2.0.18" 3921 - source = "registry+https://github.com/rust-lang/crates.io-index" 3922 - checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 3923 - dependencies = [ 3924 - "thiserror-impl 2.0.18", 3925 - ] 3926 - 3927 - [[package]] 3928 - name = "thiserror-impl" 3929 - version = "1.0.69" 3930 - source = "registry+https://github.com/rust-lang/crates.io-index" 3931 - checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 3932 - dependencies = [ 3933 - "proc-macro2", 3934 - "quote", 3935 - "syn", 3936 - ] 3937 - 3938 - [[package]] 3939 - name = "thiserror-impl" 3940 - version = "2.0.18" 3941 - source = "registry+https://github.com/rust-lang/crates.io-index" 3942 - checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 3943 - dependencies = [ 3944 - "proc-macro2", 3945 - "quote", 3946 - "syn", 3947 - ] 3948 - 3949 - [[package]] 3950 - name = "thread_local" 3951 - version = "1.1.9" 3952 - source = "registry+https://github.com/rust-lang/crates.io-index" 3953 - checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 3954 - dependencies = [ 3955 - "cfg-if", 3956 - ] 3957 - 3958 - [[package]] 3959 - name = "threadpool" 3960 - version = "1.8.1" 3961 - source = "registry+https://github.com/rust-lang/crates.io-index" 3962 - checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 3963 - dependencies = [ 3964 - "num_cpus", 3965 - ] 3966 - 3967 - [[package]] 3968 - name = "time" 3969 - version = "0.3.45" 3970 - source = "registry+https://github.com/rust-lang/crates.io-index" 3971 - checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" 3972 - dependencies = [ 3973 - "deranged", 3974 - "libc", 3975 - "num-conv", 3976 - "num_threads", 3977 - "powerfmt", 3978 - "serde_core", 3979 - "time-core", 3980 - ] 3981 - 3982 - [[package]] 3983 - name = "time-core" 3984 - version = "0.1.7" 3985 - source = "registry+https://github.com/rust-lang/crates.io-index" 3986 - checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" 3987 - 3988 - [[package]] 3989 - name = "tiny_http" 3990 - version = "0.12.0" 3991 - source = "registry+https://github.com/rust-lang/crates.io-index" 3992 - checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" 3993 - dependencies = [ 3994 - "ascii", 3995 - "chunked_transfer", 3996 - "httpdate", 3997 - "log", 3998 - ] 3999 - 4000 - [[package]] 4001 - name = "tinystr" 4002 - version = "0.8.2" 4003 - source = "registry+https://github.com/rust-lang/crates.io-index" 4004 - checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 4005 - dependencies = [ 4006 - "displaydoc", 4007 - "zerovec", 4008 - ] 4009 - 4010 - [[package]] 4011 - name = "tinyvec" 4012 - version = "1.10.0" 4013 - source = "registry+https://github.com/rust-lang/crates.io-index" 4014 - checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 4015 - dependencies = [ 4016 - "tinyvec_macros", 4017 - ] 4018 - 4019 - [[package]] 4020 - name = "tinyvec_macros" 4021 - version = "0.1.1" 4022 - source = "registry+https://github.com/rust-lang/crates.io-index" 4023 - checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 4024 - 4025 - [[package]] 4026 - name = "tokio" 4027 - version = "1.49.0" 4028 - source = "registry+https://github.com/rust-lang/crates.io-index" 4029 - checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 4030 - dependencies = [ 4031 - "bytes", 4032 - "libc", 4033 - "mio", 4034 - "parking_lot", 4035 - "pin-project-lite", 4036 - "signal-hook-registry", 4037 - "socket2 0.6.2", 4038 - "tokio-macros", 4039 - "windows-sys 0.61.2", 4040 - ] 4041 - 4042 - [[package]] 4043 - name = "tokio-macros" 4044 - version = "2.6.0" 4045 - source = "registry+https://github.com/rust-lang/crates.io-index" 4046 - checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 4047 - dependencies = [ 4048 - "proc-macro2", 4049 - "quote", 4050 - "syn", 4051 - ] 4052 - 4053 - [[package]] 4054 - name = "tokio-rustls" 4055 - version = "0.26.4" 4056 - source = "registry+https://github.com/rust-lang/crates.io-index" 4057 - checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 4058 - dependencies = [ 4059 - "rustls", 4060 - "tokio", 4061 - ] 4062 - 4063 - [[package]] 4064 - name = "tokio-tungstenite" 4065 - version = "0.24.0" 4066 - source = "registry+https://github.com/rust-lang/crates.io-index" 4067 - checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" 4068 - dependencies = [ 4069 - "futures-util", 4070 - "log", 4071 - "rustls", 4072 - "rustls-native-certs", 4073 - "rustls-pki-types", 4074 - "tokio", 4075 - "tokio-rustls", 4076 - "tungstenite", 4077 - ] 4078 - 4079 - [[package]] 4080 - name = "tokio-tungstenite-wasm" 4081 - version = "0.4.0" 4082 - source = "registry+https://github.com/rust-lang/crates.io-index" 4083 - checksum = "e21a5c399399c3db9f08d8297ac12b500e86bca82e930253fdc62eaf9c0de6ae" 4084 - dependencies = [ 4085 - "futures-channel", 4086 - "futures-util", 4087 - "http", 4088 - "httparse", 4089 - "js-sys", 4090 - "rustls", 4091 - "thiserror 1.0.69", 4092 - "tokio", 4093 - "tokio-tungstenite", 4094 - "wasm-bindgen", 4095 - "web-sys", 4096 - ] 4097 - 4098 - [[package]] 4099 - name = "tokio-util" 4100 - version = "0.7.18" 4101 - source = "registry+https://github.com/rust-lang/crates.io-index" 4102 - checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 4103 - dependencies = [ 4104 - "bytes", 4105 - "futures-core", 4106 - "futures-sink", 4107 - "futures-util", 4108 - "pin-project-lite", 4109 - "tokio", 4110 - ] 4111 - 4112 - [[package]] 4113 - name = "tower" 4114 - version = "0.5.3" 4115 - source = "registry+https://github.com/rust-lang/crates.io-index" 4116 - checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 4117 - dependencies = [ 4118 - "futures-core", 4119 - "futures-util", 4120 - "pin-project-lite", 4121 - "sync_wrapper", 4122 - "tokio", 4123 - "tower-layer", 4124 - "tower-service", 4125 - "tracing", 4126 - ] 4127 - 4128 - [[package]] 4129 - name = "tower-http" 4130 - version = "0.6.8" 4131 - source = "registry+https://github.com/rust-lang/crates.io-index" 4132 - checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 4133 - dependencies = [ 4134 - "async-compression", 4135 - "bitflags", 4136 - "bytes", 4137 - "futures-core", 4138 - "futures-util", 4139 - "http", 4140 - "http-body", 4141 - "http-body-util", 4142 - "http-range-header", 4143 - "httpdate", 4144 - "iri-string", 4145 - "mime", 4146 - "mime_guess", 4147 - "percent-encoding", 4148 - "pin-project-lite", 4149 - "tokio", 4150 - "tokio-util", 4151 - "tower", 4152 - "tower-layer", 4153 - "tower-service", 4154 - "tracing", 4155 - ] 4156 - 4157 - [[package]] 4158 - name = "tower-layer" 4159 - version = "0.3.3" 4160 - source = "registry+https://github.com/rust-lang/crates.io-index" 4161 - checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 4162 - 4163 - [[package]] 4164 - name = "tower-service" 4165 - version = "0.3.3" 4166 - source = "registry+https://github.com/rust-lang/crates.io-index" 4167 - checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 4168 - 4169 - [[package]] 4170 - name = "tracing" 4171 - version = "0.1.44" 4172 - source = "registry+https://github.com/rust-lang/crates.io-index" 4173 - checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 4174 - dependencies = [ 4175 - "log", 4176 - "pin-project-lite", 4177 - "tracing-attributes", 4178 - "tracing-core", 4179 - ] 4180 - 4181 - [[package]] 4182 - name = "tracing-attributes" 4183 - version = "0.1.31" 4184 - source = "registry+https://github.com/rust-lang/crates.io-index" 4185 - checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 4186 - dependencies = [ 4187 - "proc-macro2", 4188 - "quote", 4189 - "syn", 4190 - ] 4191 - 4192 - [[package]] 4193 - name = "tracing-core" 4194 - version = "0.1.36" 4195 - source = "registry+https://github.com/rust-lang/crates.io-index" 4196 - checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 4197 - dependencies = [ 4198 - "once_cell", 4199 - "valuable", 4200 - ] 4201 - 4202 - [[package]] 4203 - name = "tracing-log" 4204 - version = "0.2.0" 4205 - source = "registry+https://github.com/rust-lang/crates.io-index" 4206 - checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 4207 - dependencies = [ 4208 - "log", 4209 - "once_cell", 4210 - "tracing-core", 4211 - ] 4212 - 4213 - [[package]] 4214 - name = "tracing-subscriber" 4215 - version = "0.3.22" 4216 - source = "registry+https://github.com/rust-lang/crates.io-index" 4217 - checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 4218 - dependencies = [ 4219 - "matchers", 4220 - "nu-ansi-term", 4221 - "once_cell", 4222 - "regex-automata", 4223 - "sharded-slab", 4224 - "smallvec", 4225 - "thread_local", 4226 - "tracing", 4227 - "tracing-core", 4228 - "tracing-log", 4229 - ] 4230 - 4231 - [[package]] 4232 - name = "trait-variant" 4233 - version = "0.1.2" 4234 - source = "registry+https://github.com/rust-lang/crates.io-index" 4235 - checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 4236 - dependencies = [ 4237 - "proc-macro2", 4238 - "quote", 4239 - "syn", 4240 - ] 4241 - 4242 - [[package]] 4243 - name = "triomphe" 4244 - version = "0.1.15" 4245 - source = "registry+https://github.com/rust-lang/crates.io-index" 4246 - checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" 4247 - 4248 - [[package]] 4249 - name = "try-lock" 4250 - version = "0.2.5" 4251 - source = "registry+https://github.com/rust-lang/crates.io-index" 4252 - checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 4253 - 4254 - [[package]] 4255 - name = "tungstenite" 4256 - version = "0.24.0" 4257 - source = "registry+https://github.com/rust-lang/crates.io-index" 4258 - checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 4259 - dependencies = [ 4260 - "byteorder", 4261 - "bytes", 4262 - "data-encoding", 4263 - "http", 4264 - "httparse", 4265 - "log", 4266 - "rand 0.8.5", 4267 - "rustls", 4268 - "rustls-pki-types", 4269 - "sha1", 4270 - "thiserror 1.0.69", 4271 - "utf-8", 4272 - ] 4273 - 4274 - [[package]] 4275 - name = "twoway" 4276 - version = "0.1.8" 4277 - source = "registry+https://github.com/rust-lang/crates.io-index" 4278 - checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" 4279 - dependencies = [ 4280 - "memchr", 4281 - ] 4282 - 4283 - [[package]] 4284 - name = "typenum" 4285 - version = "1.19.0" 4286 - source = "registry+https://github.com/rust-lang/crates.io-index" 4287 - checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 4288 - 4289 - [[package]] 4290 - name = "unicase" 4291 - version = "2.9.0" 4292 - source = "registry+https://github.com/rust-lang/crates.io-index" 4293 - checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 4294 - 4295 - [[package]] 4296 - name = "unicode-ident" 4297 - version = "1.0.22" 4298 - source = "registry+https://github.com/rust-lang/crates.io-index" 4299 - checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 4300 - 4301 - [[package]] 4302 - name = "unicode-linebreak" 4303 - version = "0.1.5" 4304 - source = "registry+https://github.com/rust-lang/crates.io-index" 4305 - checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 4306 - 4307 - [[package]] 4308 - name = "unicode-segmentation" 4309 - version = "1.12.0" 4310 - source = "registry+https://github.com/rust-lang/crates.io-index" 4311 - checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 4312 - 4313 - [[package]] 4314 - name = "unicode-width" 4315 - version = "0.1.14" 4316 - source = "registry+https://github.com/rust-lang/crates.io-index" 4317 - checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 4318 - 4319 - [[package]] 4320 - name = "unicode-width" 4321 - version = "0.2.2" 4322 - source = "registry+https://github.com/rust-lang/crates.io-index" 4323 - checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 4324 - 4325 - [[package]] 4326 - name = "unicode-xid" 4327 - version = "0.2.6" 4328 - source = "registry+https://github.com/rust-lang/crates.io-index" 4329 - checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 4330 - 4331 - [[package]] 4332 - name = "unsigned-varint" 4333 - version = "0.8.0" 4334 - source = "registry+https://github.com/rust-lang/crates.io-index" 4335 - checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 4336 - 4337 - [[package]] 4338 - name = "untrusted" 4339 - version = "0.9.0" 4340 - source = "registry+https://github.com/rust-lang/crates.io-index" 4341 - checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 4342 - 4343 - [[package]] 4344 - name = "url" 4345 - version = "2.5.8" 4346 - source = "registry+https://github.com/rust-lang/crates.io-index" 4347 - checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 4348 - dependencies = [ 4349 - "form_urlencoded", 4350 - "idna", 4351 - "percent-encoding", 4352 - "serde", 4353 - "serde_derive", 4354 - ] 4355 - 4356 - [[package]] 4357 - name = "urlencoding" 4358 - version = "2.1.3" 4359 - source = "registry+https://github.com/rust-lang/crates.io-index" 4360 - checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 4361 - 4362 - [[package]] 4363 - name = "utf-8" 4364 - version = "0.7.6" 4365 - source = "registry+https://github.com/rust-lang/crates.io-index" 4366 - checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 4367 - 4368 - [[package]] 4369 - name = "utf8_iter" 4370 - version = "1.0.4" 4371 - source = "registry+https://github.com/rust-lang/crates.io-index" 4372 - checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 4373 - 4374 - [[package]] 4375 - name = "utf8parse" 4376 - version = "0.2.2" 4377 - source = "registry+https://github.com/rust-lang/crates.io-index" 4378 - checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 4379 - 4380 - [[package]] 4381 - name = "valuable" 4382 - version = "0.1.1" 4383 - source = "registry+https://github.com/rust-lang/crates.io-index" 4384 - checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4385 - 4386 - [[package]] 4387 - name = "version_check" 4388 - version = "0.9.5" 4389 - source = "registry+https://github.com/rust-lang/crates.io-index" 4390 - checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4391 - 4392 - [[package]] 4393 - name = "walkdir" 4394 - version = "2.5.0" 4395 - source = "registry+https://github.com/rust-lang/crates.io-index" 4396 - checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 4397 - dependencies = [ 4398 - "same-file", 4399 - "winapi-util", 4400 - ] 4401 - 4402 - [[package]] 4403 - name = "want" 4404 - version = "0.3.1" 4405 - source = "registry+https://github.com/rust-lang/crates.io-index" 4406 - checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 4407 - dependencies = [ 4408 - "try-lock", 4409 - ] 4410 - 4411 - [[package]] 4412 - name = "wasi" 4413 - version = "0.11.1+wasi-snapshot-preview1" 4414 - source = "registry+https://github.com/rust-lang/crates.io-index" 4415 - checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 4416 - 4417 - [[package]] 4418 - name = "wasip2" 4419 - version = "1.0.2+wasi-0.2.9" 4420 - source = "registry+https://github.com/rust-lang/crates.io-index" 4421 - checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" 4422 - dependencies = [ 4423 - "wit-bindgen", 4424 - ] 4425 - 4426 - [[package]] 4427 - name = "wasm-bindgen" 4428 - version = "0.2.108" 4429 - source = "registry+https://github.com/rust-lang/crates.io-index" 4430 - checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" 4431 - dependencies = [ 4432 - "cfg-if", 4433 - "once_cell", 4434 - "rustversion", 4435 - "wasm-bindgen-macro", 4436 - "wasm-bindgen-shared", 4437 - ] 4438 - 4439 - [[package]] 4440 - name = "wasm-bindgen-futures" 4441 - version = "0.4.58" 4442 - source = "registry+https://github.com/rust-lang/crates.io-index" 4443 - checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" 4444 - dependencies = [ 4445 - "cfg-if", 4446 - "futures-util", 4447 - "js-sys", 4448 - "once_cell", 4449 - "wasm-bindgen", 4450 - "web-sys", 4451 - ] 4452 - 4453 - [[package]] 4454 - name = "wasm-bindgen-macro" 4455 - version = "0.2.108" 4456 - source = "registry+https://github.com/rust-lang/crates.io-index" 4457 - checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" 4458 - dependencies = [ 4459 - "quote", 4460 - "wasm-bindgen-macro-support", 4461 - ] 4462 - 4463 - [[package]] 4464 - name = "wasm-bindgen-macro-support" 4465 - version = "0.2.108" 4466 - source = "registry+https://github.com/rust-lang/crates.io-index" 4467 - checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" 4468 - dependencies = [ 4469 - "bumpalo", 4470 - "proc-macro2", 4471 - "quote", 4472 - "syn", 4473 - "wasm-bindgen-shared", 4474 - ] 4475 - 4476 - [[package]] 4477 - name = "wasm-bindgen-shared" 4478 - version = "0.2.108" 4479 - source = "registry+https://github.com/rust-lang/crates.io-index" 4480 - checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" 4481 - dependencies = [ 4482 - "unicode-ident", 4483 - ] 4484 - 4485 - [[package]] 4486 - name = "wasm-streams" 4487 - version = "0.4.2" 4488 - source = "registry+https://github.com/rust-lang/crates.io-index" 4489 - checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4490 - dependencies = [ 4491 - "futures-util", 4492 - "js-sys", 4493 - "wasm-bindgen", 4494 - "wasm-bindgen-futures", 4495 - "web-sys", 4496 - ] 4497 - 4498 - [[package]] 4499 - name = "web-sys" 4500 - version = "0.3.85" 4501 - source = "registry+https://github.com/rust-lang/crates.io-index" 4502 - checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" 4503 - dependencies = [ 4504 - "js-sys", 4505 - "wasm-bindgen", 4506 - ] 4507 - 4508 - [[package]] 4509 - name = "web-time" 4510 - version = "1.1.0" 4511 - source = "registry+https://github.com/rust-lang/crates.io-index" 4512 - checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 4513 - dependencies = [ 4514 - "js-sys", 4515 - "wasm-bindgen", 4516 - ] 4517 - 4518 - [[package]] 4519 - name = "webbrowser" 4520 - version = "1.0.6" 4521 - source = "registry+https://github.com/rust-lang/crates.io-index" 4522 - checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" 4523 - dependencies = [ 4524 - "core-foundation 0.10.1", 4525 - "jni", 4526 - "log", 4527 - "ndk-context", 4528 - "objc2", 4529 - "objc2-foundation", 4530 - "url", 4531 - "web-sys", 4532 - ] 4533 - 4534 - [[package]] 4535 - name = "webpage" 4536 - version = "2.0.1" 4537 - source = "registry+https://github.com/rust-lang/crates.io-index" 4538 - checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac" 4539 - dependencies = [ 4540 - "html5ever", 4541 - "markup5ever_rcdom", 4542 - "serde_json", 4543 - "url", 4544 - ] 4545 - 4546 - [[package]] 4547 - name = "webpki-roots" 4548 - version = "1.0.5" 4549 - source = "registry+https://github.com/rust-lang/crates.io-index" 4550 - checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" 4551 - dependencies = [ 4552 - "rustls-pki-types", 4553 - ] 4554 - 4555 - [[package]] 4556 - name = "widestring" 4557 - version = "1.2.1" 4558 - source = "registry+https://github.com/rust-lang/crates.io-index" 4559 - checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 4560 - 4561 - [[package]] 4562 - name = "winapi-util" 4563 - version = "0.1.11" 4564 - source = "registry+https://github.com/rust-lang/crates.io-index" 4565 - checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 4566 - dependencies = [ 4567 - "windows-sys 0.61.2", 4568 - ] 4569 - 4570 - [[package]] 4571 - name = "windows-core" 4572 - version = "0.62.2" 4573 - source = "registry+https://github.com/rust-lang/crates.io-index" 4574 - checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 4575 - dependencies = [ 4576 - "windows-implement", 4577 - "windows-interface", 4578 - "windows-link", 4579 - "windows-result", 4580 - "windows-strings", 4581 - ] 4582 - 4583 - [[package]] 4584 - name = "windows-implement" 4585 - version = "0.60.2" 4586 - source = "registry+https://github.com/rust-lang/crates.io-index" 4587 - checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 4588 - dependencies = [ 4589 - "proc-macro2", 4590 - "quote", 4591 - "syn", 4592 - ] 4593 - 4594 - [[package]] 4595 - name = "windows-interface" 4596 - version = "0.59.3" 4597 - source = "registry+https://github.com/rust-lang/crates.io-index" 4598 - checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 4599 - dependencies = [ 4600 - "proc-macro2", 4601 - "quote", 4602 - "syn", 4603 - ] 4604 - 4605 - [[package]] 4606 - name = "windows-link" 4607 - version = "0.2.1" 4608 - source = "registry+https://github.com/rust-lang/crates.io-index" 4609 - checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4610 - 4611 - [[package]] 4612 - name = "windows-registry" 4613 - version = "0.6.1" 4614 - source = "registry+https://github.com/rust-lang/crates.io-index" 4615 - checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 4616 - dependencies = [ 4617 - "windows-link", 4618 - "windows-result", 4619 - "windows-strings", 4620 - ] 4621 - 4622 - [[package]] 4623 - name = "windows-result" 4624 - version = "0.4.1" 4625 - source = "registry+https://github.com/rust-lang/crates.io-index" 4626 - checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 4627 - dependencies = [ 4628 - "windows-link", 4629 - ] 4630 - 4631 - [[package]] 4632 - name = "windows-strings" 4633 - version = "0.5.1" 4634 - source = "registry+https://github.com/rust-lang/crates.io-index" 4635 - checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 4636 - dependencies = [ 4637 - "windows-link", 4638 - ] 4639 - 4640 - [[package]] 4641 - name = "windows-sys" 4642 - version = "0.45.0" 4643 - source = "registry+https://github.com/rust-lang/crates.io-index" 4644 - checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 4645 - dependencies = [ 4646 - "windows-targets 0.42.2", 4647 - ] 4648 - 4649 - [[package]] 4650 - name = "windows-sys" 4651 - version = "0.48.0" 4652 - source = "registry+https://github.com/rust-lang/crates.io-index" 4653 - checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 4654 - dependencies = [ 4655 - "windows-targets 0.48.5", 4656 - ] 4657 - 4658 - [[package]] 4659 - name = "windows-sys" 4660 - version = "0.52.0" 4661 - source = "registry+https://github.com/rust-lang/crates.io-index" 4662 - checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 4663 - dependencies = [ 4664 - "windows-targets 0.52.6", 4665 - ] 4666 - 4667 - [[package]] 4668 - name = "windows-sys" 4669 - version = "0.59.0" 4670 - source = "registry+https://github.com/rust-lang/crates.io-index" 4671 - checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 4672 - dependencies = [ 4673 - "windows-targets 0.52.6", 4674 - ] 4675 - 4676 - [[package]] 4677 - name = "windows-sys" 4678 - version = "0.60.2" 4679 - source = "registry+https://github.com/rust-lang/crates.io-index" 4680 - checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 4681 - dependencies = [ 4682 - "windows-targets 0.53.5", 4683 - ] 4684 - 4685 - [[package]] 4686 - name = "windows-sys" 4687 - version = "0.61.2" 4688 - source = "registry+https://github.com/rust-lang/crates.io-index" 4689 - checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4690 - dependencies = [ 4691 - "windows-link", 4692 - ] 4693 - 4694 - [[package]] 4695 - name = "windows-targets" 4696 - version = "0.42.2" 4697 - source = "registry+https://github.com/rust-lang/crates.io-index" 4698 - checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 4699 - dependencies = [ 4700 - "windows_aarch64_gnullvm 0.42.2", 4701 - "windows_aarch64_msvc 0.42.2", 4702 - "windows_i686_gnu 0.42.2", 4703 - "windows_i686_msvc 0.42.2", 4704 - "windows_x86_64_gnu 0.42.2", 4705 - "windows_x86_64_gnullvm 0.42.2", 4706 - "windows_x86_64_msvc 0.42.2", 4707 - ] 4708 - 4709 - [[package]] 4710 - name = "windows-targets" 4711 - version = "0.48.5" 4712 - source = "registry+https://github.com/rust-lang/crates.io-index" 4713 - checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 4714 - dependencies = [ 4715 - "windows_aarch64_gnullvm 0.48.5", 4716 - "windows_aarch64_msvc 0.48.5", 4717 - "windows_i686_gnu 0.48.5", 4718 - "windows_i686_msvc 0.48.5", 4719 - "windows_x86_64_gnu 0.48.5", 4720 - "windows_x86_64_gnullvm 0.48.5", 4721 - "windows_x86_64_msvc 0.48.5", 4722 - ] 4723 - 4724 - [[package]] 4725 - name = "windows-targets" 4726 - version = "0.52.6" 4727 - source = "registry+https://github.com/rust-lang/crates.io-index" 4728 - checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 4729 - dependencies = [ 4730 - "windows_aarch64_gnullvm 0.52.6", 4731 - "windows_aarch64_msvc 0.52.6", 4732 - "windows_i686_gnu 0.52.6", 4733 - "windows_i686_gnullvm 0.52.6", 4734 - "windows_i686_msvc 0.52.6", 4735 - "windows_x86_64_gnu 0.52.6", 4736 - "windows_x86_64_gnullvm 0.52.6", 4737 - "windows_x86_64_msvc 0.52.6", 4738 - ] 4739 - 4740 - [[package]] 4741 - name = "windows-targets" 4742 - version = "0.53.5" 4743 - source = "registry+https://github.com/rust-lang/crates.io-index" 4744 - checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 4745 - dependencies = [ 4746 - "windows-link", 4747 - "windows_aarch64_gnullvm 0.53.1", 4748 - "windows_aarch64_msvc 0.53.1", 4749 - "windows_i686_gnu 0.53.1", 4750 - "windows_i686_gnullvm 0.53.1", 4751 - "windows_i686_msvc 0.53.1", 4752 - "windows_x86_64_gnu 0.53.1", 4753 - "windows_x86_64_gnullvm 0.53.1", 4754 - "windows_x86_64_msvc 0.53.1", 4755 - ] 4756 - 4757 - [[package]] 4758 - name = "windows_aarch64_gnullvm" 4759 - version = "0.42.2" 4760 - source = "registry+https://github.com/rust-lang/crates.io-index" 4761 - checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 4762 - 4763 - [[package]] 4764 - name = "windows_aarch64_gnullvm" 4765 - version = "0.48.5" 4766 - source = "registry+https://github.com/rust-lang/crates.io-index" 4767 - checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 4768 - 4769 - [[package]] 4770 - name = "windows_aarch64_gnullvm" 4771 - version = "0.52.6" 4772 - source = "registry+https://github.com/rust-lang/crates.io-index" 4773 - checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 4774 - 4775 - [[package]] 4776 - name = "windows_aarch64_gnullvm" 4777 - version = "0.53.1" 4778 - source = "registry+https://github.com/rust-lang/crates.io-index" 4779 - checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 4780 - 4781 - [[package]] 4782 - name = "windows_aarch64_msvc" 4783 - version = "0.42.2" 4784 - source = "registry+https://github.com/rust-lang/crates.io-index" 4785 - checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 4786 - 4787 - [[package]] 4788 - name = "windows_aarch64_msvc" 4789 - version = "0.48.5" 4790 - source = "registry+https://github.com/rust-lang/crates.io-index" 4791 - checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 4792 - 4793 - [[package]] 4794 - name = "windows_aarch64_msvc" 4795 - version = "0.52.6" 4796 - source = "registry+https://github.com/rust-lang/crates.io-index" 4797 - checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 4798 - 4799 - [[package]] 4800 - name = "windows_aarch64_msvc" 4801 - version = "0.53.1" 4802 - source = "registry+https://github.com/rust-lang/crates.io-index" 4803 - checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 4804 - 4805 - [[package]] 4806 - name = "windows_i686_gnu" 4807 - version = "0.42.2" 4808 - source = "registry+https://github.com/rust-lang/crates.io-index" 4809 - checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 4810 - 4811 - [[package]] 4812 - name = "windows_i686_gnu" 4813 - version = "0.48.5" 4814 - source = "registry+https://github.com/rust-lang/crates.io-index" 4815 - checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 4816 - 4817 - [[package]] 4818 - name = "windows_i686_gnu" 4819 - version = "0.52.6" 4820 - source = "registry+https://github.com/rust-lang/crates.io-index" 4821 - checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 4822 - 4823 - [[package]] 4824 - name = "windows_i686_gnu" 4825 - version = "0.53.1" 4826 - source = "registry+https://github.com/rust-lang/crates.io-index" 4827 - checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 4828 - 4829 - [[package]] 4830 - name = "windows_i686_gnullvm" 4831 - version = "0.52.6" 4832 - source = "registry+https://github.com/rust-lang/crates.io-index" 4833 - checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 4834 - 4835 - [[package]] 4836 - name = "windows_i686_gnullvm" 4837 - version = "0.53.1" 4838 - source = "registry+https://github.com/rust-lang/crates.io-index" 4839 - checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 4840 - 4841 - [[package]] 4842 - name = "windows_i686_msvc" 4843 - version = "0.42.2" 4844 - source = "registry+https://github.com/rust-lang/crates.io-index" 4845 - checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 4846 - 4847 - [[package]] 4848 - name = "windows_i686_msvc" 4849 - version = "0.48.5" 4850 - source = "registry+https://github.com/rust-lang/crates.io-index" 4851 - checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 4852 - 4853 - [[package]] 4854 - name = "windows_i686_msvc" 4855 - version = "0.52.6" 4856 - source = "registry+https://github.com/rust-lang/crates.io-index" 4857 - checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 4858 - 4859 - [[package]] 4860 - name = "windows_i686_msvc" 4861 - version = "0.53.1" 4862 - source = "registry+https://github.com/rust-lang/crates.io-index" 4863 - checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 4864 - 4865 - [[package]] 4866 - name = "windows_x86_64_gnu" 4867 - version = "0.42.2" 4868 - source = "registry+https://github.com/rust-lang/crates.io-index" 4869 - checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 4870 - 4871 - [[package]] 4872 - name = "windows_x86_64_gnu" 4873 - version = "0.48.5" 4874 - source = "registry+https://github.com/rust-lang/crates.io-index" 4875 - checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 4876 - 4877 - [[package]] 4878 - name = "windows_x86_64_gnu" 4879 - version = "0.52.6" 4880 - source = "registry+https://github.com/rust-lang/crates.io-index" 4881 - checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 4882 - 4883 - [[package]] 4884 - name = "windows_x86_64_gnu" 4885 - version = "0.53.1" 4886 - source = "registry+https://github.com/rust-lang/crates.io-index" 4887 - checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 4888 - 4889 - [[package]] 4890 - name = "windows_x86_64_gnullvm" 4891 - version = "0.42.2" 4892 - source = "registry+https://github.com/rust-lang/crates.io-index" 4893 - checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 4894 - 4895 - [[package]] 4896 - name = "windows_x86_64_gnullvm" 4897 - version = "0.48.5" 4898 - source = "registry+https://github.com/rust-lang/crates.io-index" 4899 - checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 4900 - 4901 - [[package]] 4902 - name = "windows_x86_64_gnullvm" 4903 - version = "0.52.6" 4904 - source = "registry+https://github.com/rust-lang/crates.io-index" 4905 - checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4906 - 4907 - [[package]] 4908 - name = "windows_x86_64_gnullvm" 4909 - version = "0.53.1" 4910 - source = "registry+https://github.com/rust-lang/crates.io-index" 4911 - checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 4912 - 4913 - [[package]] 4914 - name = "windows_x86_64_msvc" 4915 - version = "0.42.2" 4916 - source = "registry+https://github.com/rust-lang/crates.io-index" 4917 - checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 4918 - 4919 - [[package]] 4920 - name = "windows_x86_64_msvc" 4921 - version = "0.48.5" 4922 - source = "registry+https://github.com/rust-lang/crates.io-index" 4923 - checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 4924 - 4925 - [[package]] 4926 - name = "windows_x86_64_msvc" 4927 - version = "0.52.6" 4928 - source = "registry+https://github.com/rust-lang/crates.io-index" 4929 - checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 4930 - 4931 - [[package]] 4932 - name = "windows_x86_64_msvc" 4933 - version = "0.53.1" 4934 - source = "registry+https://github.com/rust-lang/crates.io-index" 4935 - checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 4936 - 4937 - [[package]] 4938 - name = "winreg" 4939 - version = "0.50.0" 4940 - source = "registry+https://github.com/rust-lang/crates.io-index" 4941 - checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 4942 - dependencies = [ 4943 - "cfg-if", 4944 - "windows-sys 0.48.0", 4945 - ] 4946 - 4947 - [[package]] 4948 - name = "wisp-cli" 4949 - version = "0.5.0" 4950 - dependencies = [ 4951 - "axum", 4952 - "base64 0.22.1", 4953 - "bytes", 4954 - "chrono", 4955 - "clap", 4956 - "flate2", 4957 - "futures", 4958 - "globset", 4959 - "ignore", 4960 - "indicatif", 4961 - "jacquard", 4962 - "jacquard-api", 4963 - "jacquard-common", 4964 - "jacquard-derive", 4965 - "jacquard-identity", 4966 - "jacquard-lexicon", 4967 - "jacquard-oauth", 4968 - "miette", 4969 - "mime_guess", 4970 - "multibase", 4971 - "multihash", 4972 - "n0-future 0.3.2", 4973 - "regex", 4974 - "reqwest", 4975 - "rustversion", 4976 - "serde", 4977 - "serde_json", 4978 - "sha2", 4979 - "shellexpand", 4980 - "tokio", 4981 - "tower", 4982 - "tower-http", 4983 - "url", 4984 - "walkdir", 4985 - "wisp-lexicons", 4986 - ] 4987 - 4988 - [[package]] 4989 - name = "wisp-lexicons" 4990 - version = "0.1.0" 4991 - dependencies = [ 4992 - "jacquard-common", 4993 - "jacquard-derive", 4994 - "jacquard-lexicon", 4995 - "rustversion", 4996 - "serde", 4997 - ] 4998 - 4999 - [[package]] 5000 - name = "wit-bindgen" 5001 - version = "0.51.0" 5002 - source = "registry+https://github.com/rust-lang/crates.io-index" 5003 - checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 5004 - 5005 - [[package]] 5006 - name = "writeable" 5007 - version = "0.6.2" 5008 - source = "registry+https://github.com/rust-lang/crates.io-index" 5009 - checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 5010 - 5011 - [[package]] 5012 - name = "xml5ever" 5013 - version = "0.18.1" 5014 - source = "registry+https://github.com/rust-lang/crates.io-index" 5015 - checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" 5016 - dependencies = [ 5017 - "log", 5018 - "mac", 5019 - "markup5ever", 5020 - ] 5021 - 5022 - [[package]] 5023 - name = "yansi" 5024 - version = "1.0.1" 5025 - source = "registry+https://github.com/rust-lang/crates.io-index" 5026 - checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 5027 - 5028 - [[package]] 5029 - name = "yoke" 5030 - version = "0.8.1" 5031 - source = "registry+https://github.com/rust-lang/crates.io-index" 5032 - checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 5033 - dependencies = [ 5034 - "stable_deref_trait", 5035 - "yoke-derive", 5036 - "zerofrom", 5037 - ] 5038 - 5039 - [[package]] 5040 - name = "yoke-derive" 5041 - version = "0.8.1" 5042 - source = "registry+https://github.com/rust-lang/crates.io-index" 5043 - checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 5044 - dependencies = [ 5045 - "proc-macro2", 5046 - "quote", 5047 - "syn", 5048 - "synstructure", 5049 - ] 5050 - 5051 - [[package]] 5052 - name = "zerocopy" 5053 - version = "0.8.33" 5054 - source = "registry+https://github.com/rust-lang/crates.io-index" 5055 - checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" 5056 - dependencies = [ 5057 - "zerocopy-derive", 5058 - ] 5059 - 5060 - [[package]] 5061 - name = "zerocopy-derive" 5062 - version = "0.8.33" 5063 - source = "registry+https://github.com/rust-lang/crates.io-index" 5064 - checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" 5065 - dependencies = [ 5066 - "proc-macro2", 5067 - "quote", 5068 - "syn", 5069 - ] 5070 - 5071 - [[package]] 5072 - name = "zerofrom" 5073 - version = "0.1.6" 5074 - source = "registry+https://github.com/rust-lang/crates.io-index" 5075 - checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 5076 - dependencies = [ 5077 - "zerofrom-derive", 5078 - ] 5079 - 5080 - [[package]] 5081 - name = "zerofrom-derive" 5082 - version = "0.1.6" 5083 - source = "registry+https://github.com/rust-lang/crates.io-index" 5084 - checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 5085 - dependencies = [ 5086 - "proc-macro2", 5087 - "quote", 5088 - "syn", 5089 - "synstructure", 5090 - ] 5091 - 5092 - [[package]] 5093 - name = "zeroize" 5094 - version = "1.8.2" 5095 - source = "registry+https://github.com/rust-lang/crates.io-index" 5096 - checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 5097 - dependencies = [ 5098 - "serde", 5099 - ] 5100 - 5101 - [[package]] 5102 - name = "zerotrie" 5103 - version = "0.2.3" 5104 - source = "registry+https://github.com/rust-lang/crates.io-index" 5105 - checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 5106 - dependencies = [ 5107 - "displaydoc", 5108 - "yoke", 5109 - "zerofrom", 5110 - ] 5111 - 5112 - [[package]] 5113 - name = "zerovec" 5114 - version = "0.11.5" 5115 - source = "registry+https://github.com/rust-lang/crates.io-index" 5116 - checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 5117 - dependencies = [ 5118 - "yoke", 5119 - "zerofrom", 5120 - "zerovec-derive", 5121 - ] 5122 - 5123 - [[package]] 5124 - name = "zerovec-derive" 5125 - version = "0.11.2" 5126 - source = "registry+https://github.com/rust-lang/crates.io-index" 5127 - checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 5128 - dependencies = [ 5129 - "proc-macro2", 5130 - "quote", 5131 - "syn", 5132 - ] 5133 - 5134 - [[package]] 5135 - name = "zmij" 5136 - version = "1.0.16" 5137 - source = "registry+https://github.com/rust-lang/crates.io-index" 5138 - checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
-53
rust-cli/Cargo.toml
··· 1 - [package] 2 - name = "wisp-cli" 3 - version = "0.5.0" 4 - edition = "2024" 5 - 6 - [features] 7 - default = ["place_wisp"] 8 - place_wisp = [] 9 - 10 - [dependencies] 11 - jacquard = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["loopback"] } 12 - jacquard-oauth = { git = "https://tangled.org/nonbinary.computer/jacquard" } 13 - jacquard-api = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["streaming"] } 14 - jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["websocket"] } 15 - jacquard-identity = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["dns"] } 16 - jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" } 17 - jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" } 18 - wisp-lexicons = { path = "crates/lexicons" } 19 - #jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] } 20 - #jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" } 21 - #jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] } 22 - #jacquard-common = { path = "../../jacquard/crates/jacquard-common", features = ["websocket"] } 23 - #jacquard-identity = { path = "../../jacquard/crates/jacquard-identity", features = ["dns"] } 24 - #jacquard-derive = { path = "../../jacquard/crates/jacquard-derive" } 25 - #jacquard-lexicon = { path = "../../jacquard/crates/jacquard-lexicon" } 26 - clap = { version = "4.5.51", features = ["derive"] } 27 - tokio = { version = "1.48", features = ["full"] } 28 - miette = { version = "7.6.0", features = ["fancy"] } 29 - serde_json = "1.0.145" 30 - serde = { version = "1.0", features = ["derive"] } 31 - shellexpand = "3.1.1" 32 - #reqwest = "0.12" 33 - reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } 34 - rustversion = "1.0" 35 - flate2 = "1.0" 36 - base64 = "0.22" 37 - walkdir = "2.5" 38 - mime_guess = "2.0" 39 - bytes = "1.10" 40 - futures = "0.3.31" 41 - multihash = "0.19.3" 42 - multibase = "0.9" 43 - sha2 = "0.10" 44 - axum = "0.8.7" 45 - tower-http = { version = "0.6.6", features = ["fs", "compression-gzip"] } 46 - tower = "0.5.2" 47 - n0-future = "0.3.1" 48 - chrono = "0.4" 49 - url = "2.5" 50 - regex = "1.11" 51 - ignore = "0.4" 52 - globset = "0.4" 53 - indicatif = "0.17"
-341
rust-cli/README.md
··· 1 - # Wisp CLI 2 - 3 - A command-line tool for deploying static sites to your AT Protocol repo to be served on [wisp.place](https://wisp.place), an AT indexer to serve such sites. 4 - 5 - ## Why? 6 - 7 - The PDS serves as a way to verfiably, cryptographically prove that you own your site. That it was you (or at least someone who controls your account) who uploaded it. It is also a manifest of each file in the site to ensure file integrity. Keeping hosting seperate ensures that you could move your site across other servers or even serverless solutions to ensure speedy delievery while keeping it backed by an absolute source of truth being the manifest record and the blobs of each file in your repo. 8 - 9 - ## Features 10 - 11 - - Deploy static sites directly to your AT Protocol repo 12 - - Supports both OAuth and app password authentication 13 - - Preserves directory structure and file integrity 14 - 15 - ## Soon 16 - 17 - -- Host sites 18 - -- Manage and delete sites 19 - -- Metrics and logs for self hosting. 20 - 21 - ## Installation 22 - 23 - ### From Source 24 - 25 - ```bash 26 - cargo build --release 27 - ``` 28 - 29 - Check out the build scripts for cross complation using nix-shell. 30 - 31 - The binary will be available at `target/release/wisp-cli`. 32 - 33 - ## Usage 34 - 35 - ### Commands 36 - 37 - The CLI supports three main commands: 38 - - **deploy**: Upload a site to your PDS (default command) 39 - - **pull**: Download a site from a PDS to a local directory 40 - - **serve**: Serve a site locally with real-time firehose updates 41 - 42 - ### Basic Deployment 43 - 44 - Deploy the current directory: 45 - 46 - ```bash 47 - wisp-cli nekomimi.pet --path . --site my-site 48 - ``` 49 - 50 - Deploy a specific directory: 51 - 52 - ```bash 53 - wisp-cli alice.bsky.social --path ./dist/ --site my-site 54 - ``` 55 - 56 - Or use the explicit `deploy` subcommand: 57 - 58 - ```bash 59 - wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site 60 - ``` 61 - 62 - ### Pull a Site 63 - 64 - Download a site from a PDS to a local directory: 65 - 66 - ```bash 67 - wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site 68 - ``` 69 - 70 - This will download all files from the site to the specified directory. 71 - 72 - ### Serve a Site Locally 73 - 74 - Serve a site locally with real-time updates from the firehose: 75 - 76 - ```bash 77 - wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080 78 - ``` 79 - 80 - This will: 81 - 1. Download the site to the specified path 82 - 2. Start a local server on the specified port (default: 8080) 83 - 3. Watch the firehose for updates and automatically reload files when changed 84 - 85 - ### Authentication Methods 86 - 87 - #### OAuth (Recommended) 88 - 89 - By default, the CLI uses OAuth authentication with a local loopback server: 90 - 91 - ```bash 92 - wisp-cli alice.bsky.social --path ./my-site --site my-site 93 - ``` 94 - 95 - This will: 96 - 1. Open your browser for authentication 97 - 2. Save the session to a file (default: `/tmp/wisp-oauth-session.json`) 98 - 3. Reuse the session for future deployments 99 - 100 - Specify a custom session file location: 101 - 102 - ```bash 103 - wisp-cli alice.bsky.social --path ./my-site --site my-site --store ~/.wisp-session.json 104 - ``` 105 - 106 - #### App Password 107 - 108 - For headless environments or CI/CD, use an app password: 109 - 110 - ```bash 111 - wisp-cli alice.bsky.social --path ./my-site --site my-site --password YOUR_APP_PASSWORD 112 - ``` 113 - 114 - **Note:** When using `--password`, the `--store` option is ignored. 115 - 116 - ## Command-Line Options 117 - 118 - ### Deploy Command 119 - 120 - ``` 121 - wisp-cli [deploy] [OPTIONS] <INPUT> 122 - 123 - Arguments: 124 - <INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL 125 - 126 - Options: 127 - -p, --path <PATH> Path to the directory containing your static site [default: .] 128 - -s, --site <SITE> Site name (defaults to directory name) 129 - --store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json] 130 - --password <PASSWORD> App Password for authentication (alternative to OAuth) 131 - --directory Enable directory listing mode for paths without index files 132 - --spa Enable SPA mode (serve index.html for all routes) 133 - -y, --yes Skip confirmation prompts (automatically accept warnings) 134 - -h, --help Print help 135 - -V, --version Print version 136 - ``` 137 - 138 - ### Pull Command 139 - 140 - ``` 141 - wisp-cli pull [OPTIONS] --site <SITE> <INPUT> 142 - 143 - Arguments: 144 - <INPUT> Handle (e.g., alice.bsky.social) or DID 145 - 146 - Options: 147 - -s, --site <SITE> Site name (record key) 148 - -p, --path <PATH> Output directory for the downloaded site [default: .] 149 - -h, --help Print help 150 - ``` 151 - 152 - ### Serve Command 153 - 154 - ``` 155 - wisp-cli serve [OPTIONS] --site <SITE> <INPUT> 156 - 157 - Arguments: 158 - <INPUT> Handle (e.g., alice.bsky.social) or DID 159 - 160 - Options: 161 - -s, --site <SITE> Site name (record key) 162 - -p, --path <PATH> Output directory for the site files [default: .] 163 - -P, --port <PORT> Port to serve on [default: 8080] 164 - -h, --help Print help 165 - ``` 166 - 167 - ## How It Works 168 - 169 - 1. **Authentication**: Authenticates using OAuth or app password 170 - 2. **File Processing**: 171 - - Recursively walks the directory tree 172 - - Skips hidden files (starting with `.`) 173 - - Detects MIME types automatically 174 - - Compresses files with gzip 175 - - Base64 encodes compressed content 176 - 3. **Upload**: 177 - - Uploads files as blobs to your PDS 178 - - Processes up to 5 files concurrently 179 - - Creates a `place.wisp.fs` record with the site manifest 180 - 4. **Deployment**: Site is immediately available at `https://sites.wisp.place/{did}/{site-name}` 181 - 182 - ## File Processing 183 - 184 - All files are automatically: 185 - 186 - - **Compressed** with gzip (level 9) 187 - - **Base64 encoded** to bypass PDS content sniffing 188 - - **Uploaded** as `application/octet-stream` blobs 189 - - **Stored** with original MIME type metadata 190 - 191 - The hosting service automatically decompresses non HTML/CSS/JS files when serving them. 192 - 193 - ## Limitations 194 - 195 - - **Max file size**: 100MB per file (after compression) (this is a PDS limit, but not enforced by the CLI in case yours is higher) 196 - - **Max file count**: 2000 files 197 - - **Site name** must follow AT Protocol rkey format rules (alphanumeric, hyphens, underscores) 198 - 199 - ## Deploy with CI/CD 200 - 201 - ### GitHub Actions 202 - 203 - ```yaml 204 - name: Deploy to Wisp 205 - on: 206 - push: 207 - branches: [main] 208 - 209 - jobs: 210 - deploy: 211 - runs-on: ubuntu-latest 212 - steps: 213 - - uses: actions/checkout@v3 214 - 215 - - name: Setup Node 216 - uses: actions/setup-node@v3 217 - with: 218 - node-version: '25' 219 - 220 - - name: Install dependencies 221 - run: npm install 222 - 223 - - name: Build site 224 - run: npm run build 225 - 226 - - name: Download Wisp CLI 227 - run: | 228 - curl -L https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 229 - chmod +x wisp-cli 230 - 231 - - name: Deploy to Wisp 232 - env: 233 - WISP_APP_PASSWORD: ${{ secrets.WISP_APP_PASSWORD }} 234 - run: | 235 - ./wisp-cli alice.bsky.social \ 236 - --path ./dist \ 237 - --site my-site \ 238 - --password "$WISP_APP_PASSWORD" 239 - ``` 240 - 241 - ### Tangled.org 242 - 243 - ```yaml 244 - when: 245 - - event: ['push'] 246 - branch: ['main'] 247 - - event: ['manual'] 248 - 249 - engine: 'nixery' 250 - 251 - clone: 252 - skip: false 253 - depth: 1 254 - submodules: false 255 - 256 - dependencies: 257 - nixpkgs: 258 - - nodejs 259 - - coreutils 260 - - curl 261 - github:NixOS/nixpkgs/nixpkgs-unstable: 262 - - bun 263 - 264 - environment: 265 - SITE_PATH: 'dist' 266 - SITE_NAME: 'my-site' 267 - WISP_HANDLE: 'your-handle.bsky.social' 268 - 269 - steps: 270 - - name: build site 271 - command: | 272 - export PATH="$HOME/.nix-profile/bin:$PATH" 273 - 274 - # regenerate lockfile 275 - rm package-lock.json bun.lock 276 - bun install @rolldown/binding-linux-arm64-gnu --save-optional 277 - bun install 278 - 279 - # build with vite 280 - bun node_modules/.bin/vite build 281 - 282 - - name: deploy to wisp 283 - command: | 284 - # Download Wisp CLI 285 - curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli 286 - chmod +x wisp-cli 287 - 288 - # Deploy to Wisp 289 - ./wisp-cli \ 290 - "$WISP_HANDLE" \ 291 - --path "$SITE_PATH" \ 292 - --site "$SITE_NAME" \ 293 - --password "$WISP_APP_PASSWORD" 294 - ``` 295 - 296 - ### Generic Shell Script 297 - 298 - ```bash 299 - # Use app password from environment variable 300 - wisp-cli alice.bsky.social --path ./dist --site my-site --password "$WISP_APP_PASSWORD" 301 - ``` 302 - 303 - ## Output 304 - 305 - Upon successful deployment, you'll see: 306 - 307 - ``` 308 - Deployed site 'my-site': at://did:plc:abc123xyz/place.wisp.fs/my-site 309 - Available at: https://sites.wisp.place/did:plc:abc123xyz/my-site 310 - ``` 311 - 312 - ### Dependencies 313 - 314 - - **jacquard**: AT Protocol client library 315 - - **clap**: Command-line argument parsing 316 - - **tokio**: Async runtime 317 - - **flate2**: Gzip compression 318 - - **base64**: Base64 encoding 319 - - **walkdir**: Directory traversal 320 - - **mime_guess**: MIME type detection 321 - 322 - ## License 323 - 324 - MIT License 325 - 326 - ## Contributing 327 - 328 - Just don't give me entirely claude slop especailly not in the PR description itself. You should be responsible for code you submit and aware of what it even is you're submitting. 329 - 330 - ## Links 331 - 332 - - **Website**: https://wisp.place 333 - - **Main Repository**: https://tangled.org/@nekomimi.pet/wisp.place-monorepo 334 - - **AT Protocol**: https://atproto.com 335 - - **Jacquard Library**: https://tangled.org/@nonbinary.computer/jacquard 336 - 337 - ## Support 338 - 339 - For issues and questions: 340 - - Check the main wisp.place documentation 341 - - Open an issue in the main repository
-23
rust-cli/build-linux.sh
··· 1 - #!/usr/bin/env bash 2 - # Build Linux binaries (statically linked) 3 - set -e 4 - mkdir -p binaries 5 - 6 - # Build Linux binaries 7 - echo "Building Linux binaries..." 8 - 9 - echo "Building Linux ARM64 (static)..." 10 - nix-shell -p rustup --run ' 11 - rustup target add aarch64-unknown-linux-musl 12 - RUSTFLAGS="-C target-feature=+crt-static" cargo zigbuild --release --target aarch64-unknown-linux-musl 13 - ' 14 - cp target/aarch64-unknown-linux-musl/release/wisp-cli binaries/wisp-cli-aarch64-linux 15 - 16 - echo "Building Linux x86_64 (static)..." 17 - nix-shell -p rustup --run ' 18 - rustup target add x86_64-unknown-linux-musl 19 - RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target x86_64-unknown-linux-musl 20 - ' 21 - cp target/x86_64-unknown-linux-musl/release/wisp-cli binaries/wisp-cli-x86_64-linux 22 - 23 - echo "Done! Binaries in ./binaries/"
-33
rust-cli/build-macos.sh
··· 1 - #!/bin/bash 2 - # Build macOS universal binary (arm64 + x86_64) 3 - 4 - set -e 5 - 6 - mkdir -p binaries 7 - rm -rf target 8 - 9 - echo "Building macOS universal binary..." 10 - 11 - # Add both targets 12 - rustup target add aarch64-apple-darwin x86_64-apple-darwin 13 - 14 - # Build arm64 15 - echo "Building macOS arm64..." 16 - RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target aarch64-apple-darwin 17 - 18 - # Build x86_64 19 - echo "Building macOS x86_64..." 20 - RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target x86_64-apple-darwin 21 - 22 - # Create universal binary with lipo 23 - echo "Creating universal binary..." 24 - lipo -create \ 25 - target/aarch64-apple-darwin/release/wisp-cli \ 26 - target/x86_64-apple-darwin/release/wisp-cli \ 27 - -output binaries/wisp-cli-darwin-universal 28 - 29 - # Also keep individual binaries if needed 30 - cp target/aarch64-apple-darwin/release/wisp-cli binaries/wisp-cli-darwin-arm64 31 - cp target/x86_64-apple-darwin/release/wisp-cli binaries/wisp-cli-darwin-x86_64 32 - 33 - echo "Done! Universal binary: binaries/wisp-cli-darwin-universal"
-15
rust-cli/crates/lexicons/Cargo.toml
··· 1 - [package] 2 - name = "wisp-lexicons" 3 - version = "0.1.0" 4 - edition = "2024" 5 - 6 - [features] 7 - default = ["place_wisp"] 8 - place_wisp = [] 9 - 10 - [dependencies] 11 - jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard" } 12 - jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" } 13 - jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" } 14 - serde = { version = "1.0", features = ["derive"] } 15 - rustversion = "1.0"
-43
rust-cli/crates/lexicons/src/builder_types.rs
··· 1 - // @generated by jacquard-lexicon. DO NOT EDIT. 2 - // 3 - // This file was automatically generated from Lexicon schemas. 4 - // Any manual changes will be overwritten on the next regeneration. 5 - 6 - /// Marker type indicating a builder field has been set 7 - pub struct Set<T>(pub T); 8 - impl<T> Set<T> { 9 - /// Extract the inner value 10 - #[inline] 11 - pub fn into_inner(self) -> T { 12 - self.0 13 - } 14 - } 15 - 16 - /// Marker type indicating a builder field has not been set 17 - pub struct Unset; 18 - /// Trait indicating a builder field is set (has a value) 19 - #[rustversion::attr( 20 - since(1.78.0), 21 - diagnostic::on_unimplemented( 22 - message = "the field `{Self}` was not set, but this method requires it to be set", 23 - label = "the field `{Self}` was not set" 24 - ) 25 - )] 26 - pub trait IsSet: private::Sealed {} 27 - /// Trait indicating a builder field is unset (no value yet) 28 - #[rustversion::attr( 29 - since(1.78.0), 30 - diagnostic::on_unimplemented( 31 - message = "the field `{Self}` was already set, but this method requires it to be unset", 32 - label = "the field `{Self}` was already set" 33 - ) 34 - )] 35 - pub trait IsUnset: private::Sealed {} 36 - impl<T> IsSet for Set<T> {} 37 - impl IsUnset for Unset {} 38 - mod private { 39 - /// Sealed trait to prevent external implementations 40 - pub trait Sealed {} 41 - impl<T> Sealed for super::Set<T> {} 42 - impl Sealed for super::Unset {} 43 - }
-11
rust-cli/crates/lexicons/src/lib.rs
··· 1 - extern crate alloc; 2 - 3 - // @generated by jacquard-lexicon. DO NOT EDIT. 4 - // 5 - // This file was automatically generated from Lexicon schemas. 6 - // Any manual changes will be overwritten on the next regeneration. 7 - 8 - pub mod builder_types; 9 - 10 - #[cfg(feature = "place_wisp")] 11 - pub mod place_wisp;
-8
rust-cli/crates/lexicons/src/place_wisp.rs
··· 1 - // @generated by jacquard-lexicon. DO NOT EDIT. 2 - // 3 - // This file was automatically generated from Lexicon schemas. 4 - // Any manual changes will be overwritten on the next regeneration. 5 - 6 - pub mod fs; 7 - pub mod settings; 8 - pub mod subfs;
-1490
rust-cli/crates/lexicons/src/place_wisp/fs.rs
··· 1 - // @generated by jacquard-lexicon. DO NOT EDIT. 2 - // 3 - // Lexicon: place.wisp.fs 4 - // 5 - // This file was automatically generated from Lexicon schemas. 6 - // Any manual changes will be overwritten on the next regeneration. 7 - 8 - #[jacquard_derive::lexicon] 9 - #[derive( 10 - serde::Serialize, 11 - serde::Deserialize, 12 - Debug, 13 - Clone, 14 - PartialEq, 15 - Eq, 16 - jacquard_derive::IntoStatic 17 - )] 18 - #[serde(rename_all = "camelCase")] 19 - pub struct Directory<'a> { 20 - #[serde(borrow)] 21 - pub entries: Vec<crate::place_wisp::fs::Entry<'a>>, 22 - #[serde(borrow)] 23 - pub r#type: jacquard_common::CowStr<'a>, 24 - } 25 - 26 - pub mod directory_state { 27 - 28 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 29 - #[allow(unused)] 30 - use ::core::marker::PhantomData; 31 - mod sealed { 32 - pub trait Sealed {} 33 - } 34 - /// State trait tracking which required fields have been set 35 - pub trait State: sealed::Sealed { 36 - type Type; 37 - type Entries; 38 - } 39 - /// Empty state - all required fields are unset 40 - pub struct Empty(()); 41 - impl sealed::Sealed for Empty {} 42 - impl State for Empty { 43 - type Type = Unset; 44 - type Entries = Unset; 45 - } 46 - ///State transition - sets the `type` field to Set 47 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 48 - impl<S: State> sealed::Sealed for SetType<S> {} 49 - impl<S: State> State for SetType<S> { 50 - type Type = Set<members::r#type>; 51 - type Entries = S::Entries; 52 - } 53 - ///State transition - sets the `entries` field to Set 54 - pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>); 55 - impl<S: State> sealed::Sealed for SetEntries<S> {} 56 - impl<S: State> State for SetEntries<S> { 57 - type Type = S::Type; 58 - type Entries = Set<members::entries>; 59 - } 60 - /// Marker types for field names 61 - #[allow(non_camel_case_types)] 62 - pub mod members { 63 - ///Marker type for the `type` field 64 - pub struct r#type(()); 65 - ///Marker type for the `entries` field 66 - pub struct entries(()); 67 - } 68 - } 69 - 70 - /// Builder for constructing an instance of this type 71 - pub struct DirectoryBuilder<'a, S: directory_state::State> { 72 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 73 - __unsafe_private_named: ( 74 - ::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>, 75 - ::core::option::Option<jacquard_common::CowStr<'a>>, 76 - ), 77 - _phantom: ::core::marker::PhantomData<&'a ()>, 78 - } 79 - 80 - impl<'a> Directory<'a> { 81 - /// Create a new builder for this type 82 - pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> { 83 - DirectoryBuilder::new() 84 - } 85 - } 86 - 87 - impl<'a> DirectoryBuilder<'a, directory_state::Empty> { 88 - /// Create a new builder with all fields unset 89 - pub fn new() -> Self { 90 - DirectoryBuilder { 91 - _phantom_state: ::core::marker::PhantomData, 92 - __unsafe_private_named: (None, None), 93 - _phantom: ::core::marker::PhantomData, 94 - } 95 - } 96 - } 97 - 98 - impl<'a, S> DirectoryBuilder<'a, S> 99 - where 100 - S: directory_state::State, 101 - S::Entries: directory_state::IsUnset, 102 - { 103 - /// Set the `entries` field (required) 104 - pub fn entries( 105 - mut self, 106 - value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>, 107 - ) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> { 108 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 109 - DirectoryBuilder { 110 - _phantom_state: ::core::marker::PhantomData, 111 - __unsafe_private_named: self.__unsafe_private_named, 112 - _phantom: ::core::marker::PhantomData, 113 - } 114 - } 115 - } 116 - 117 - impl<'a, S> DirectoryBuilder<'a, S> 118 - where 119 - S: directory_state::State, 120 - S::Type: directory_state::IsUnset, 121 - { 122 - /// Set the `type` field (required) 123 - pub fn r#type( 124 - mut self, 125 - value: impl Into<jacquard_common::CowStr<'a>>, 126 - ) -> DirectoryBuilder<'a, directory_state::SetType<S>> { 127 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 128 - DirectoryBuilder { 129 - _phantom_state: ::core::marker::PhantomData, 130 - __unsafe_private_named: self.__unsafe_private_named, 131 - _phantom: ::core::marker::PhantomData, 132 - } 133 - } 134 - } 135 - 136 - impl<'a, S> DirectoryBuilder<'a, S> 137 - where 138 - S: directory_state::State, 139 - S::Type: directory_state::IsSet, 140 - S::Entries: directory_state::IsSet, 141 - { 142 - /// Build the final struct 143 - pub fn build(self) -> Directory<'a> { 144 - Directory { 145 - entries: self.__unsafe_private_named.0.unwrap(), 146 - r#type: self.__unsafe_private_named.1.unwrap(), 147 - extra_data: Default::default(), 148 - } 149 - } 150 - /// Build the final struct with custom extra_data 151 - pub fn build_with_data( 152 - self, 153 - extra_data: std::collections::BTreeMap< 154 - jacquard_common::smol_str::SmolStr, 155 - jacquard_common::types::value::Data<'a>, 156 - >, 157 - ) -> Directory<'a> { 158 - Directory { 159 - entries: self.__unsafe_private_named.0.unwrap(), 160 - r#type: self.__unsafe_private_named.1.unwrap(), 161 - extra_data: Some(extra_data), 162 - } 163 - } 164 - } 165 - 166 - fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 167 - ::jacquard_lexicon::lexicon::LexiconDoc { 168 - lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 169 - id: ::jacquard_common::CowStr::new_static("place.wisp.fs"), 170 - revision: None, 171 - description: None, 172 - defs: { 173 - let mut map = ::std::collections::BTreeMap::new(); 174 - map.insert( 175 - ::jacquard_common::smol_str::SmolStr::new_static("directory"), 176 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 177 - description: None, 178 - required: Some( 179 - vec![ 180 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 181 - ::jacquard_common::smol_str::SmolStr::new_static("entries") 182 - ], 183 - ), 184 - nullable: None, 185 - properties: { 186 - #[allow(unused_mut)] 187 - let mut map = ::std::collections::BTreeMap::new(); 188 - map.insert( 189 - ::jacquard_common::smol_str::SmolStr::new_static("entries"), 190 - ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 191 - description: None, 192 - items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 193 - description: None, 194 - r#ref: ::jacquard_common::CowStr::new_static("#entry"), 195 - }), 196 - min_length: None, 197 - max_length: Some(500usize), 198 - }), 199 - ); 200 - map.insert( 201 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 202 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 203 - description: None, 204 - format: None, 205 - default: None, 206 - min_length: None, 207 - max_length: None, 208 - min_graphemes: None, 209 - max_graphemes: None, 210 - r#enum: None, 211 - r#const: None, 212 - known_values: None, 213 - }), 214 - ); 215 - map 216 - }, 217 - }), 218 - ); 219 - map.insert( 220 - ::jacquard_common::smol_str::SmolStr::new_static("entry"), 221 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 222 - description: None, 223 - required: Some( 224 - vec![ 225 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 226 - ::jacquard_common::smol_str::SmolStr::new_static("node") 227 - ], 228 - ), 229 - nullable: None, 230 - properties: { 231 - #[allow(unused_mut)] 232 - let mut map = ::std::collections::BTreeMap::new(); 233 - map.insert( 234 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 235 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 236 - description: None, 237 - format: None, 238 - default: None, 239 - min_length: None, 240 - max_length: Some(255usize), 241 - min_graphemes: None, 242 - max_graphemes: None, 243 - r#enum: None, 244 - r#const: None, 245 - known_values: None, 246 - }), 247 - ); 248 - map.insert( 249 - ::jacquard_common::smol_str::SmolStr::new_static("node"), 250 - ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 251 - description: None, 252 - refs: vec![ 253 - ::jacquard_common::CowStr::new_static("#file"), 254 - ::jacquard_common::CowStr::new_static("#directory"), 255 - ::jacquard_common::CowStr::new_static("#subfs") 256 - ], 257 - closed: None, 258 - }), 259 - ); 260 - map 261 - }, 262 - }), 263 - ); 264 - map.insert( 265 - ::jacquard_common::smol_str::SmolStr::new_static("file"), 266 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 267 - description: None, 268 - required: Some( 269 - vec![ 270 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 271 - ::jacquard_common::smol_str::SmolStr::new_static("blob") 272 - ], 273 - ), 274 - nullable: None, 275 - properties: { 276 - #[allow(unused_mut)] 277 - let mut map = ::std::collections::BTreeMap::new(); 278 - map.insert( 279 - ::jacquard_common::smol_str::SmolStr::new_static("base64"), 280 - ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 281 - description: None, 282 - default: None, 283 - r#const: None, 284 - }), 285 - ); 286 - map.insert( 287 - ::jacquard_common::smol_str::SmolStr::new_static("blob"), 288 - ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 289 - description: None, 290 - accept: None, 291 - max_size: None, 292 - }), 293 - ); 294 - map.insert( 295 - ::jacquard_common::smol_str::SmolStr::new_static("encoding"), 296 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 297 - description: Some( 298 - ::jacquard_common::CowStr::new_static( 299 - "Content encoding (e.g., gzip for compressed files)", 300 - ), 301 - ), 302 - format: None, 303 - default: None, 304 - min_length: None, 305 - max_length: None, 306 - min_graphemes: None, 307 - max_graphemes: None, 308 - r#enum: None, 309 - r#const: None, 310 - known_values: None, 311 - }), 312 - ); 313 - map.insert( 314 - ::jacquard_common::smol_str::SmolStr::new_static("mimeType"), 315 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 316 - description: Some( 317 - ::jacquard_common::CowStr::new_static( 318 - "Original MIME type before compression", 319 - ), 320 - ), 321 - format: None, 322 - default: None, 323 - min_length: None, 324 - max_length: None, 325 - min_graphemes: None, 326 - max_graphemes: None, 327 - r#enum: None, 328 - r#const: None, 329 - known_values: None, 330 - }), 331 - ); 332 - map.insert( 333 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 334 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 335 - description: None, 336 - format: None, 337 - default: None, 338 - min_length: None, 339 - max_length: None, 340 - min_graphemes: None, 341 - max_graphemes: None, 342 - r#enum: None, 343 - r#const: None, 344 - known_values: None, 345 - }), 346 - ); 347 - map 348 - }, 349 - }), 350 - ); 351 - map.insert( 352 - ::jacquard_common::smol_str::SmolStr::new_static("main"), 353 - ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 354 - description: Some( 355 - ::jacquard_common::CowStr::new_static( 356 - "Virtual filesystem manifest for a Wisp site", 357 - ), 358 - ), 359 - key: None, 360 - record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 361 - description: None, 362 - required: Some( 363 - vec![ 364 - ::jacquard_common::smol_str::SmolStr::new_static("site"), 365 - ::jacquard_common::smol_str::SmolStr::new_static("root"), 366 - ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 367 - ], 368 - ), 369 - nullable: None, 370 - properties: { 371 - #[allow(unused_mut)] 372 - let mut map = ::std::collections::BTreeMap::new(); 373 - map.insert( 374 - ::jacquard_common::smol_str::SmolStr::new_static( 375 - "createdAt", 376 - ), 377 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 378 - description: None, 379 - format: Some( 380 - ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 381 - ), 382 - default: None, 383 - min_length: None, 384 - max_length: None, 385 - min_graphemes: None, 386 - max_graphemes: None, 387 - r#enum: None, 388 - r#const: None, 389 - known_values: None, 390 - }), 391 - ); 392 - map.insert( 393 - ::jacquard_common::smol_str::SmolStr::new_static( 394 - "fileCount", 395 - ), 396 - ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 397 - description: None, 398 - default: None, 399 - minimum: Some(0i64), 400 - maximum: Some(1000i64), 401 - r#enum: None, 402 - r#const: None, 403 - }), 404 - ); 405 - map.insert( 406 - ::jacquard_common::smol_str::SmolStr::new_static("root"), 407 - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 408 - description: None, 409 - r#ref: ::jacquard_common::CowStr::new_static("#directory"), 410 - }), 411 - ); 412 - map.insert( 413 - ::jacquard_common::smol_str::SmolStr::new_static("site"), 414 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 415 - description: None, 416 - format: None, 417 - default: None, 418 - min_length: None, 419 - max_length: None, 420 - min_graphemes: None, 421 - max_graphemes: None, 422 - r#enum: None, 423 - r#const: None, 424 - known_values: None, 425 - }), 426 - ); 427 - map 428 - }, 429 - }), 430 - }), 431 - ); 432 - map.insert( 433 - ::jacquard_common::smol_str::SmolStr::new_static("subfs"), 434 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 435 - description: None, 436 - required: Some( 437 - vec![ 438 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 439 - ::jacquard_common::smol_str::SmolStr::new_static("subject") 440 - ], 441 - ), 442 - nullable: None, 443 - properties: { 444 - #[allow(unused_mut)] 445 - let mut map = ::std::collections::BTreeMap::new(); 446 - map.insert( 447 - ::jacquard_common::smol_str::SmolStr::new_static("flat"), 448 - ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 449 - description: None, 450 - default: None, 451 - r#const: None, 452 - }), 453 - ); 454 - map.insert( 455 - ::jacquard_common::smol_str::SmolStr::new_static("subject"), 456 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 457 - description: Some( 458 - ::jacquard_common::CowStr::new_static( 459 - "AT-URI pointing to a place.wisp.subfs record containing this subtree.", 460 - ), 461 - ), 462 - format: Some( 463 - ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, 464 - ), 465 - default: None, 466 - min_length: None, 467 - max_length: None, 468 - min_graphemes: None, 469 - max_graphemes: None, 470 - r#enum: None, 471 - r#const: None, 472 - known_values: None, 473 - }), 474 - ); 475 - map.insert( 476 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 477 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 478 - description: None, 479 - format: None, 480 - default: None, 481 - min_length: None, 482 - max_length: None, 483 - min_graphemes: None, 484 - max_graphemes: None, 485 - r#enum: None, 486 - r#const: None, 487 - known_values: None, 488 - }), 489 - ); 490 - map 491 - }, 492 - }), 493 - ); 494 - map 495 - }, 496 - } 497 - } 498 - 499 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> { 500 - fn nsid() -> &'static str { 501 - "place.wisp.fs" 502 - } 503 - fn def_name() -> &'static str { 504 - "directory" 505 - } 506 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 507 - lexicon_doc_place_wisp_fs() 508 - } 509 - fn validate( 510 - &self, 511 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 512 - { 513 - let value = &self.entries; 514 - #[allow(unused_comparisons)] 515 - if value.len() > 500usize { 516 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 517 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 518 - "entries", 519 - ), 520 - max: 500usize, 521 - actual: value.len(), 522 - }); 523 - } 524 - } 525 - Ok(()) 526 - } 527 - } 528 - 529 - #[jacquard_derive::lexicon] 530 - #[derive( 531 - serde::Serialize, 532 - serde::Deserialize, 533 - Debug, 534 - Clone, 535 - PartialEq, 536 - Eq, 537 - jacquard_derive::IntoStatic 538 - )] 539 - #[serde(rename_all = "camelCase")] 540 - pub struct Entry<'a> { 541 - #[serde(borrow)] 542 - pub name: jacquard_common::CowStr<'a>, 543 - #[serde(borrow)] 544 - pub node: EntryNode<'a>, 545 - } 546 - 547 - pub mod entry_state { 548 - 549 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 550 - #[allow(unused)] 551 - use ::core::marker::PhantomData; 552 - mod sealed { 553 - pub trait Sealed {} 554 - } 555 - /// State trait tracking which required fields have been set 556 - pub trait State: sealed::Sealed { 557 - type Node; 558 - type Name; 559 - } 560 - /// Empty state - all required fields are unset 561 - pub struct Empty(()); 562 - impl sealed::Sealed for Empty {} 563 - impl State for Empty { 564 - type Node = Unset; 565 - type Name = Unset; 566 - } 567 - ///State transition - sets the `node` field to Set 568 - pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>); 569 - impl<S: State> sealed::Sealed for SetNode<S> {} 570 - impl<S: State> State for SetNode<S> { 571 - type Node = Set<members::node>; 572 - type Name = S::Name; 573 - } 574 - ///State transition - sets the `name` field to Set 575 - pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>); 576 - impl<S: State> sealed::Sealed for SetName<S> {} 577 - impl<S: State> State for SetName<S> { 578 - type Node = S::Node; 579 - type Name = Set<members::name>; 580 - } 581 - /// Marker types for field names 582 - #[allow(non_camel_case_types)] 583 - pub mod members { 584 - ///Marker type for the `node` field 585 - pub struct node(()); 586 - ///Marker type for the `name` field 587 - pub struct name(()); 588 - } 589 - } 590 - 591 - /// Builder for constructing an instance of this type 592 - pub struct EntryBuilder<'a, S: entry_state::State> { 593 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 594 - __unsafe_private_named: ( 595 - ::core::option::Option<jacquard_common::CowStr<'a>>, 596 - ::core::option::Option<EntryNode<'a>>, 597 - ), 598 - _phantom: ::core::marker::PhantomData<&'a ()>, 599 - } 600 - 601 - impl<'a> Entry<'a> { 602 - /// Create a new builder for this type 603 - pub fn new() -> EntryBuilder<'a, entry_state::Empty> { 604 - EntryBuilder::new() 605 - } 606 - } 607 - 608 - impl<'a> EntryBuilder<'a, entry_state::Empty> { 609 - /// Create a new builder with all fields unset 610 - pub fn new() -> Self { 611 - EntryBuilder { 612 - _phantom_state: ::core::marker::PhantomData, 613 - __unsafe_private_named: (None, None), 614 - _phantom: ::core::marker::PhantomData, 615 - } 616 - } 617 - } 618 - 619 - impl<'a, S> EntryBuilder<'a, S> 620 - where 621 - S: entry_state::State, 622 - S::Name: entry_state::IsUnset, 623 - { 624 - /// Set the `name` field (required) 625 - pub fn name( 626 - mut self, 627 - value: impl Into<jacquard_common::CowStr<'a>>, 628 - ) -> EntryBuilder<'a, entry_state::SetName<S>> { 629 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 630 - EntryBuilder { 631 - _phantom_state: ::core::marker::PhantomData, 632 - __unsafe_private_named: self.__unsafe_private_named, 633 - _phantom: ::core::marker::PhantomData, 634 - } 635 - } 636 - } 637 - 638 - impl<'a, S> EntryBuilder<'a, S> 639 - where 640 - S: entry_state::State, 641 - S::Node: entry_state::IsUnset, 642 - { 643 - /// Set the `node` field (required) 644 - pub fn node( 645 - mut self, 646 - value: impl Into<EntryNode<'a>>, 647 - ) -> EntryBuilder<'a, entry_state::SetNode<S>> { 648 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 649 - EntryBuilder { 650 - _phantom_state: ::core::marker::PhantomData, 651 - __unsafe_private_named: self.__unsafe_private_named, 652 - _phantom: ::core::marker::PhantomData, 653 - } 654 - } 655 - } 656 - 657 - impl<'a, S> EntryBuilder<'a, S> 658 - where 659 - S: entry_state::State, 660 - S::Node: entry_state::IsSet, 661 - S::Name: entry_state::IsSet, 662 - { 663 - /// Build the final struct 664 - pub fn build(self) -> Entry<'a> { 665 - Entry { 666 - name: self.__unsafe_private_named.0.unwrap(), 667 - node: self.__unsafe_private_named.1.unwrap(), 668 - extra_data: Default::default(), 669 - } 670 - } 671 - /// Build the final struct with custom extra_data 672 - pub fn build_with_data( 673 - self, 674 - extra_data: std::collections::BTreeMap< 675 - jacquard_common::smol_str::SmolStr, 676 - jacquard_common::types::value::Data<'a>, 677 - >, 678 - ) -> Entry<'a> { 679 - Entry { 680 - name: self.__unsafe_private_named.0.unwrap(), 681 - node: self.__unsafe_private_named.1.unwrap(), 682 - extra_data: Some(extra_data), 683 - } 684 - } 685 - } 686 - 687 - #[jacquard_derive::open_union] 688 - #[derive( 689 - serde::Serialize, 690 - serde::Deserialize, 691 - Debug, 692 - Clone, 693 - PartialEq, 694 - Eq, 695 - jacquard_derive::IntoStatic 696 - )] 697 - #[serde(tag = "$type")] 698 - #[serde(bound(deserialize = "'de: 'a"))] 699 - pub enum EntryNode<'a> { 700 - #[serde(rename = "place.wisp.fs#file")] 701 - File(Box<crate::place_wisp::fs::File<'a>>), 702 - #[serde(rename = "place.wisp.fs#directory")] 703 - Directory(Box<crate::place_wisp::fs::Directory<'a>>), 704 - #[serde(rename = "place.wisp.fs#subfs")] 705 - Subfs(Box<crate::place_wisp::fs::Subfs<'a>>), 706 - } 707 - 708 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> { 709 - fn nsid() -> &'static str { 710 - "place.wisp.fs" 711 - } 712 - fn def_name() -> &'static str { 713 - "entry" 714 - } 715 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 716 - lexicon_doc_place_wisp_fs() 717 - } 718 - fn validate( 719 - &self, 720 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 721 - { 722 - let value = &self.name; 723 - #[allow(unused_comparisons)] 724 - if <str>::len(value.as_ref()) > 255usize { 725 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 726 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 727 - "name", 728 - ), 729 - max: 255usize, 730 - actual: <str>::len(value.as_ref()), 731 - }); 732 - } 733 - } 734 - Ok(()) 735 - } 736 - } 737 - 738 - #[jacquard_derive::lexicon] 739 - #[derive( 740 - serde::Serialize, 741 - serde::Deserialize, 742 - Debug, 743 - Clone, 744 - PartialEq, 745 - Eq, 746 - jacquard_derive::IntoStatic 747 - )] 748 - #[serde(rename_all = "camelCase")] 749 - pub struct File<'a> { 750 - /// True if blob content is base64-encoded (used to bypass PDS content sniffing) 751 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 752 - pub base64: std::option::Option<bool>, 753 - /// Content blob ref 754 - #[serde(borrow)] 755 - pub blob: jacquard_common::types::blob::BlobRef<'a>, 756 - /// Content encoding (e.g., gzip for compressed files) 757 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 758 - #[serde(borrow)] 759 - pub encoding: std::option::Option<jacquard_common::CowStr<'a>>, 760 - /// Original MIME type before compression 761 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 762 - #[serde(borrow)] 763 - pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>, 764 - #[serde(borrow)] 765 - pub r#type: jacquard_common::CowStr<'a>, 766 - } 767 - 768 - pub mod file_state { 769 - 770 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 771 - #[allow(unused)] 772 - use ::core::marker::PhantomData; 773 - mod sealed { 774 - pub trait Sealed {} 775 - } 776 - /// State trait tracking which required fields have been set 777 - pub trait State: sealed::Sealed { 778 - type Type; 779 - type Blob; 780 - } 781 - /// Empty state - all required fields are unset 782 - pub struct Empty(()); 783 - impl sealed::Sealed for Empty {} 784 - impl State for Empty { 785 - type Type = Unset; 786 - type Blob = Unset; 787 - } 788 - ///State transition - sets the `type` field to Set 789 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 790 - impl<S: State> sealed::Sealed for SetType<S> {} 791 - impl<S: State> State for SetType<S> { 792 - type Type = Set<members::r#type>; 793 - type Blob = S::Blob; 794 - } 795 - ///State transition - sets the `blob` field to Set 796 - pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>); 797 - impl<S: State> sealed::Sealed for SetBlob<S> {} 798 - impl<S: State> State for SetBlob<S> { 799 - type Type = S::Type; 800 - type Blob = Set<members::blob>; 801 - } 802 - /// Marker types for field names 803 - #[allow(non_camel_case_types)] 804 - pub mod members { 805 - ///Marker type for the `type` field 806 - pub struct r#type(()); 807 - ///Marker type for the `blob` field 808 - pub struct blob(()); 809 - } 810 - } 811 - 812 - /// Builder for constructing an instance of this type 813 - pub struct FileBuilder<'a, S: file_state::State> { 814 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 815 - __unsafe_private_named: ( 816 - ::core::option::Option<bool>, 817 - ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 818 - ::core::option::Option<jacquard_common::CowStr<'a>>, 819 - ::core::option::Option<jacquard_common::CowStr<'a>>, 820 - ::core::option::Option<jacquard_common::CowStr<'a>>, 821 - ), 822 - _phantom: ::core::marker::PhantomData<&'a ()>, 823 - } 824 - 825 - impl<'a> File<'a> { 826 - /// Create a new builder for this type 827 - pub fn new() -> FileBuilder<'a, file_state::Empty> { 828 - FileBuilder::new() 829 - } 830 - } 831 - 832 - impl<'a> FileBuilder<'a, file_state::Empty> { 833 - /// Create a new builder with all fields unset 834 - pub fn new() -> Self { 835 - FileBuilder { 836 - _phantom_state: ::core::marker::PhantomData, 837 - __unsafe_private_named: (None, None, None, None, None), 838 - _phantom: ::core::marker::PhantomData, 839 - } 840 - } 841 - } 842 - 843 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 844 - /// Set the `base64` field (optional) 845 - pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self { 846 - self.__unsafe_private_named.0 = value.into(); 847 - self 848 - } 849 - /// Set the `base64` field to an Option value (optional) 850 - pub fn maybe_base64(mut self, value: Option<bool>) -> Self { 851 - self.__unsafe_private_named.0 = value; 852 - self 853 - } 854 - } 855 - 856 - impl<'a, S> FileBuilder<'a, S> 857 - where 858 - S: file_state::State, 859 - S::Blob: file_state::IsUnset, 860 - { 861 - /// Set the `blob` field (required) 862 - pub fn blob( 863 - mut self, 864 - value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 865 - ) -> FileBuilder<'a, file_state::SetBlob<S>> { 866 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 867 - FileBuilder { 868 - _phantom_state: ::core::marker::PhantomData, 869 - __unsafe_private_named: self.__unsafe_private_named, 870 - _phantom: ::core::marker::PhantomData, 871 - } 872 - } 873 - } 874 - 875 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 876 - /// Set the `encoding` field (optional) 877 - pub fn encoding( 878 - mut self, 879 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 880 - ) -> Self { 881 - self.__unsafe_private_named.2 = value.into(); 882 - self 883 - } 884 - /// Set the `encoding` field to an Option value (optional) 885 - pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 886 - self.__unsafe_private_named.2 = value; 887 - self 888 - } 889 - } 890 - 891 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 892 - /// Set the `mimeType` field (optional) 893 - pub fn mime_type( 894 - mut self, 895 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 896 - ) -> Self { 897 - self.__unsafe_private_named.3 = value.into(); 898 - self 899 - } 900 - /// Set the `mimeType` field to an Option value (optional) 901 - pub fn maybe_mime_type( 902 - mut self, 903 - value: Option<jacquard_common::CowStr<'a>>, 904 - ) -> Self { 905 - self.__unsafe_private_named.3 = value; 906 - self 907 - } 908 - } 909 - 910 - impl<'a, S> FileBuilder<'a, S> 911 - where 912 - S: file_state::State, 913 - S::Type: file_state::IsUnset, 914 - { 915 - /// Set the `type` field (required) 916 - pub fn r#type( 917 - mut self, 918 - value: impl Into<jacquard_common::CowStr<'a>>, 919 - ) -> FileBuilder<'a, file_state::SetType<S>> { 920 - self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 921 - FileBuilder { 922 - _phantom_state: ::core::marker::PhantomData, 923 - __unsafe_private_named: self.__unsafe_private_named, 924 - _phantom: ::core::marker::PhantomData, 925 - } 926 - } 927 - } 928 - 929 - impl<'a, S> FileBuilder<'a, S> 930 - where 931 - S: file_state::State, 932 - S::Type: file_state::IsSet, 933 - S::Blob: file_state::IsSet, 934 - { 935 - /// Build the final struct 936 - pub fn build(self) -> File<'a> { 937 - File { 938 - base64: self.__unsafe_private_named.0, 939 - blob: self.__unsafe_private_named.1.unwrap(), 940 - encoding: self.__unsafe_private_named.2, 941 - mime_type: self.__unsafe_private_named.3, 942 - r#type: self.__unsafe_private_named.4.unwrap(), 943 - extra_data: Default::default(), 944 - } 945 - } 946 - /// Build the final struct with custom extra_data 947 - pub fn build_with_data( 948 - self, 949 - extra_data: std::collections::BTreeMap< 950 - jacquard_common::smol_str::SmolStr, 951 - jacquard_common::types::value::Data<'a>, 952 - >, 953 - ) -> File<'a> { 954 - File { 955 - base64: self.__unsafe_private_named.0, 956 - blob: self.__unsafe_private_named.1.unwrap(), 957 - encoding: self.__unsafe_private_named.2, 958 - mime_type: self.__unsafe_private_named.3, 959 - r#type: self.__unsafe_private_named.4.unwrap(), 960 - extra_data: Some(extra_data), 961 - } 962 - } 963 - } 964 - 965 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> { 966 - fn nsid() -> &'static str { 967 - "place.wisp.fs" 968 - } 969 - fn def_name() -> &'static str { 970 - "file" 971 - } 972 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 973 - lexicon_doc_place_wisp_fs() 974 - } 975 - fn validate( 976 - &self, 977 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 978 - Ok(()) 979 - } 980 - } 981 - 982 - /// Virtual filesystem manifest for a Wisp site 983 - #[jacquard_derive::lexicon] 984 - #[derive( 985 - serde::Serialize, 986 - serde::Deserialize, 987 - Debug, 988 - Clone, 989 - PartialEq, 990 - Eq, 991 - jacquard_derive::IntoStatic 992 - )] 993 - #[serde(rename_all = "camelCase")] 994 - pub struct Fs<'a> { 995 - pub created_at: jacquard_common::types::string::Datetime, 996 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 997 - pub file_count: std::option::Option<i64>, 998 - #[serde(borrow)] 999 - pub root: crate::place_wisp::fs::Directory<'a>, 1000 - #[serde(borrow)] 1001 - pub site: jacquard_common::CowStr<'a>, 1002 - } 1003 - 1004 - pub mod fs_state { 1005 - 1006 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1007 - #[allow(unused)] 1008 - use ::core::marker::PhantomData; 1009 - mod sealed { 1010 - pub trait Sealed {} 1011 - } 1012 - /// State trait tracking which required fields have been set 1013 - pub trait State: sealed::Sealed { 1014 - type CreatedAt; 1015 - type Site; 1016 - type Root; 1017 - } 1018 - /// Empty state - all required fields are unset 1019 - pub struct Empty(()); 1020 - impl sealed::Sealed for Empty {} 1021 - impl State for Empty { 1022 - type CreatedAt = Unset; 1023 - type Site = Unset; 1024 - type Root = Unset; 1025 - } 1026 - ///State transition - sets the `created_at` field to Set 1027 - pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 1028 - impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 1029 - impl<S: State> State for SetCreatedAt<S> { 1030 - type CreatedAt = Set<members::created_at>; 1031 - type Site = S::Site; 1032 - type Root = S::Root; 1033 - } 1034 - ///State transition - sets the `site` field to Set 1035 - pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>); 1036 - impl<S: State> sealed::Sealed for SetSite<S> {} 1037 - impl<S: State> State for SetSite<S> { 1038 - type CreatedAt = S::CreatedAt; 1039 - type Site = Set<members::site>; 1040 - type Root = S::Root; 1041 - } 1042 - ///State transition - sets the `root` field to Set 1043 - pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>); 1044 - impl<S: State> sealed::Sealed for SetRoot<S> {} 1045 - impl<S: State> State for SetRoot<S> { 1046 - type CreatedAt = S::CreatedAt; 1047 - type Site = S::Site; 1048 - type Root = Set<members::root>; 1049 - } 1050 - /// Marker types for field names 1051 - #[allow(non_camel_case_types)] 1052 - pub mod members { 1053 - ///Marker type for the `created_at` field 1054 - pub struct created_at(()); 1055 - ///Marker type for the `site` field 1056 - pub struct site(()); 1057 - ///Marker type for the `root` field 1058 - pub struct root(()); 1059 - } 1060 - } 1061 - 1062 - /// Builder for constructing an instance of this type 1063 - pub struct FsBuilder<'a, S: fs_state::State> { 1064 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1065 - __unsafe_private_named: ( 1066 - ::core::option::Option<jacquard_common::types::string::Datetime>, 1067 - ::core::option::Option<i64>, 1068 - ::core::option::Option<crate::place_wisp::fs::Directory<'a>>, 1069 - ::core::option::Option<jacquard_common::CowStr<'a>>, 1070 - ), 1071 - _phantom: ::core::marker::PhantomData<&'a ()>, 1072 - } 1073 - 1074 - impl<'a> Fs<'a> { 1075 - /// Create a new builder for this type 1076 - pub fn new() -> FsBuilder<'a, fs_state::Empty> { 1077 - FsBuilder::new() 1078 - } 1079 - } 1080 - 1081 - impl<'a> FsBuilder<'a, fs_state::Empty> { 1082 - /// Create a new builder with all fields unset 1083 - pub fn new() -> Self { 1084 - FsBuilder { 1085 - _phantom_state: ::core::marker::PhantomData, 1086 - __unsafe_private_named: (None, None, None, None), 1087 - _phantom: ::core::marker::PhantomData, 1088 - } 1089 - } 1090 - } 1091 - 1092 - impl<'a, S> FsBuilder<'a, S> 1093 - where 1094 - S: fs_state::State, 1095 - S::CreatedAt: fs_state::IsUnset, 1096 - { 1097 - /// Set the `createdAt` field (required) 1098 - pub fn created_at( 1099 - mut self, 1100 - value: impl Into<jacquard_common::types::string::Datetime>, 1101 - ) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> { 1102 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1103 - FsBuilder { 1104 - _phantom_state: ::core::marker::PhantomData, 1105 - __unsafe_private_named: self.__unsafe_private_named, 1106 - _phantom: ::core::marker::PhantomData, 1107 - } 1108 - } 1109 - } 1110 - 1111 - impl<'a, S: fs_state::State> FsBuilder<'a, S> { 1112 - /// Set the `fileCount` field (optional) 1113 - pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self { 1114 - self.__unsafe_private_named.1 = value.into(); 1115 - self 1116 - } 1117 - /// Set the `fileCount` field to an Option value (optional) 1118 - pub fn maybe_file_count(mut self, value: Option<i64>) -> Self { 1119 - self.__unsafe_private_named.1 = value; 1120 - self 1121 - } 1122 - } 1123 - 1124 - impl<'a, S> FsBuilder<'a, S> 1125 - where 1126 - S: fs_state::State, 1127 - S::Root: fs_state::IsUnset, 1128 - { 1129 - /// Set the `root` field (required) 1130 - pub fn root( 1131 - mut self, 1132 - value: impl Into<crate::place_wisp::fs::Directory<'a>>, 1133 - ) -> FsBuilder<'a, fs_state::SetRoot<S>> { 1134 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1135 - FsBuilder { 1136 - _phantom_state: ::core::marker::PhantomData, 1137 - __unsafe_private_named: self.__unsafe_private_named, 1138 - _phantom: ::core::marker::PhantomData, 1139 - } 1140 - } 1141 - } 1142 - 1143 - impl<'a, S> FsBuilder<'a, S> 1144 - where 1145 - S: fs_state::State, 1146 - S::Site: fs_state::IsUnset, 1147 - { 1148 - /// Set the `site` field (required) 1149 - pub fn site( 1150 - mut self, 1151 - value: impl Into<jacquard_common::CowStr<'a>>, 1152 - ) -> FsBuilder<'a, fs_state::SetSite<S>> { 1153 - self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into()); 1154 - FsBuilder { 1155 - _phantom_state: ::core::marker::PhantomData, 1156 - __unsafe_private_named: self.__unsafe_private_named, 1157 - _phantom: ::core::marker::PhantomData, 1158 - } 1159 - } 1160 - } 1161 - 1162 - impl<'a, S> FsBuilder<'a, S> 1163 - where 1164 - S: fs_state::State, 1165 - S::CreatedAt: fs_state::IsSet, 1166 - S::Site: fs_state::IsSet, 1167 - S::Root: fs_state::IsSet, 1168 - { 1169 - /// Build the final struct 1170 - pub fn build(self) -> Fs<'a> { 1171 - Fs { 1172 - created_at: self.__unsafe_private_named.0.unwrap(), 1173 - file_count: self.__unsafe_private_named.1, 1174 - root: self.__unsafe_private_named.2.unwrap(), 1175 - site: self.__unsafe_private_named.3.unwrap(), 1176 - extra_data: Default::default(), 1177 - } 1178 - } 1179 - /// Build the final struct with custom extra_data 1180 - pub fn build_with_data( 1181 - self, 1182 - extra_data: std::collections::BTreeMap< 1183 - jacquard_common::smol_str::SmolStr, 1184 - jacquard_common::types::value::Data<'a>, 1185 - >, 1186 - ) -> Fs<'a> { 1187 - Fs { 1188 - created_at: self.__unsafe_private_named.0.unwrap(), 1189 - file_count: self.__unsafe_private_named.1, 1190 - root: self.__unsafe_private_named.2.unwrap(), 1191 - site: self.__unsafe_private_named.3.unwrap(), 1192 - extra_data: Some(extra_data), 1193 - } 1194 - } 1195 - } 1196 - 1197 - impl<'a> Fs<'a> { 1198 - pub fn uri( 1199 - uri: impl Into<jacquard_common::CowStr<'a>>, 1200 - ) -> Result< 1201 - jacquard_common::types::uri::RecordUri<'a, FsRecord>, 1202 - jacquard_common::types::uri::UriError, 1203 - > { 1204 - jacquard_common::types::uri::RecordUri::try_from_uri( 1205 - jacquard_common::types::string::AtUri::new_cow(uri.into())?, 1206 - ) 1207 - } 1208 - } 1209 - 1210 - /// Typed wrapper for GetRecord response with this collection's record type. 1211 - #[derive( 1212 - serde::Serialize, 1213 - serde::Deserialize, 1214 - Debug, 1215 - Clone, 1216 - PartialEq, 1217 - Eq, 1218 - jacquard_derive::IntoStatic 1219 - )] 1220 - #[serde(rename_all = "camelCase")] 1221 - pub struct FsGetRecordOutput<'a> { 1222 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 1223 - #[serde(borrow)] 1224 - pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 1225 - #[serde(borrow)] 1226 - pub uri: jacquard_common::types::string::AtUri<'a>, 1227 - #[serde(borrow)] 1228 - pub value: Fs<'a>, 1229 - } 1230 - 1231 - impl From<FsGetRecordOutput<'_>> for Fs<'_> { 1232 - fn from(output: FsGetRecordOutput<'_>) -> Self { 1233 - use jacquard_common::IntoStatic; 1234 - output.value.into_static() 1235 - } 1236 - } 1237 - 1238 - impl jacquard_common::types::collection::Collection for Fs<'_> { 1239 - const NSID: &'static str = "place.wisp.fs"; 1240 - type Record = FsRecord; 1241 - } 1242 - 1243 - /// Marker type for deserializing records from this collection. 1244 - #[derive(Debug, serde::Serialize, serde::Deserialize)] 1245 - pub struct FsRecord; 1246 - impl jacquard_common::xrpc::XrpcResp for FsRecord { 1247 - const NSID: &'static str = "place.wisp.fs"; 1248 - const ENCODING: &'static str = "application/json"; 1249 - type Output<'de> = FsGetRecordOutput<'de>; 1250 - type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 1251 - } 1252 - 1253 - impl jacquard_common::types::collection::Collection for FsRecord { 1254 - const NSID: &'static str = "place.wisp.fs"; 1255 - type Record = FsRecord; 1256 - } 1257 - 1258 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> { 1259 - fn nsid() -> &'static str { 1260 - "place.wisp.fs" 1261 - } 1262 - fn def_name() -> &'static str { 1263 - "main" 1264 - } 1265 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1266 - lexicon_doc_place_wisp_fs() 1267 - } 1268 - fn validate( 1269 - &self, 1270 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1271 - if let Some(ref value) = self.file_count { 1272 - if *value > 1000i64 { 1273 - return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { 1274 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 1275 - "file_count", 1276 - ), 1277 - max: 1000i64, 1278 - actual: *value, 1279 - }); 1280 - } 1281 - } 1282 - if let Some(ref value) = self.file_count { 1283 - if *value < 0i64 { 1284 - return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { 1285 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 1286 - "file_count", 1287 - ), 1288 - min: 0i64, 1289 - actual: *value, 1290 - }); 1291 - } 1292 - } 1293 - Ok(()) 1294 - } 1295 - } 1296 - 1297 - #[jacquard_derive::lexicon] 1298 - #[derive( 1299 - serde::Serialize, 1300 - serde::Deserialize, 1301 - Debug, 1302 - Clone, 1303 - PartialEq, 1304 - Eq, 1305 - jacquard_derive::IntoStatic 1306 - )] 1307 - #[serde(rename_all = "camelCase")] 1308 - pub struct Subfs<'a> { 1309 - /// If true (default), the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false, the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure. 1310 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 1311 - pub flat: std::option::Option<bool>, 1312 - /// AT-URI pointing to a place.wisp.subfs record containing this subtree. 1313 - #[serde(borrow)] 1314 - pub subject: jacquard_common::types::string::AtUri<'a>, 1315 - #[serde(borrow)] 1316 - pub r#type: jacquard_common::CowStr<'a>, 1317 - } 1318 - 1319 - pub mod subfs_state { 1320 - 1321 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1322 - #[allow(unused)] 1323 - use ::core::marker::PhantomData; 1324 - mod sealed { 1325 - pub trait Sealed {} 1326 - } 1327 - /// State trait tracking which required fields have been set 1328 - pub trait State: sealed::Sealed { 1329 - type Type; 1330 - type Subject; 1331 - } 1332 - /// Empty state - all required fields are unset 1333 - pub struct Empty(()); 1334 - impl sealed::Sealed for Empty {} 1335 - impl State for Empty { 1336 - type Type = Unset; 1337 - type Subject = Unset; 1338 - } 1339 - ///State transition - sets the `type` field to Set 1340 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 1341 - impl<S: State> sealed::Sealed for SetType<S> {} 1342 - impl<S: State> State for SetType<S> { 1343 - type Type = Set<members::r#type>; 1344 - type Subject = S::Subject; 1345 - } 1346 - ///State transition - sets the `subject` field to Set 1347 - pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 1348 - impl<S: State> sealed::Sealed for SetSubject<S> {} 1349 - impl<S: State> State for SetSubject<S> { 1350 - type Type = S::Type; 1351 - type Subject = Set<members::subject>; 1352 - } 1353 - /// Marker types for field names 1354 - #[allow(non_camel_case_types)] 1355 - pub mod members { 1356 - ///Marker type for the `type` field 1357 - pub struct r#type(()); 1358 - ///Marker type for the `subject` field 1359 - pub struct subject(()); 1360 - } 1361 - } 1362 - 1363 - /// Builder for constructing an instance of this type 1364 - pub struct SubfsBuilder<'a, S: subfs_state::State> { 1365 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1366 - __unsafe_private_named: ( 1367 - ::core::option::Option<bool>, 1368 - ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 1369 - ::core::option::Option<jacquard_common::CowStr<'a>>, 1370 - ), 1371 - _phantom: ::core::marker::PhantomData<&'a ()>, 1372 - } 1373 - 1374 - impl<'a> Subfs<'a> { 1375 - /// Create a new builder for this type 1376 - pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> { 1377 - SubfsBuilder::new() 1378 - } 1379 - } 1380 - 1381 - impl<'a> SubfsBuilder<'a, subfs_state::Empty> { 1382 - /// Create a new builder with all fields unset 1383 - pub fn new() -> Self { 1384 - SubfsBuilder { 1385 - _phantom_state: ::core::marker::PhantomData, 1386 - __unsafe_private_named: (None, None, None), 1387 - _phantom: ::core::marker::PhantomData, 1388 - } 1389 - } 1390 - } 1391 - 1392 - impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> { 1393 - /// Set the `flat` field (optional) 1394 - pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self { 1395 - self.__unsafe_private_named.0 = value.into(); 1396 - self 1397 - } 1398 - /// Set the `flat` field to an Option value (optional) 1399 - pub fn maybe_flat(mut self, value: Option<bool>) -> Self { 1400 - self.__unsafe_private_named.0 = value; 1401 - self 1402 - } 1403 - } 1404 - 1405 - impl<'a, S> SubfsBuilder<'a, S> 1406 - where 1407 - S: subfs_state::State, 1408 - S::Subject: subfs_state::IsUnset, 1409 - { 1410 - /// Set the `subject` field (required) 1411 - pub fn subject( 1412 - mut self, 1413 - value: impl Into<jacquard_common::types::string::AtUri<'a>>, 1414 - ) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> { 1415 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 1416 - SubfsBuilder { 1417 - _phantom_state: ::core::marker::PhantomData, 1418 - __unsafe_private_named: self.__unsafe_private_named, 1419 - _phantom: ::core::marker::PhantomData, 1420 - } 1421 - } 1422 - } 1423 - 1424 - impl<'a, S> SubfsBuilder<'a, S> 1425 - where 1426 - S: subfs_state::State, 1427 - S::Type: subfs_state::IsUnset, 1428 - { 1429 - /// Set the `type` field (required) 1430 - pub fn r#type( 1431 - mut self, 1432 - value: impl Into<jacquard_common::CowStr<'a>>, 1433 - ) -> SubfsBuilder<'a, subfs_state::SetType<S>> { 1434 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1435 - SubfsBuilder { 1436 - _phantom_state: ::core::marker::PhantomData, 1437 - __unsafe_private_named: self.__unsafe_private_named, 1438 - _phantom: ::core::marker::PhantomData, 1439 - } 1440 - } 1441 - } 1442 - 1443 - impl<'a, S> SubfsBuilder<'a, S> 1444 - where 1445 - S: subfs_state::State, 1446 - S::Type: subfs_state::IsSet, 1447 - S::Subject: subfs_state::IsSet, 1448 - { 1449 - /// Build the final struct 1450 - pub fn build(self) -> Subfs<'a> { 1451 - Subfs { 1452 - flat: self.__unsafe_private_named.0, 1453 - subject: self.__unsafe_private_named.1.unwrap(), 1454 - r#type: self.__unsafe_private_named.2.unwrap(), 1455 - extra_data: Default::default(), 1456 - } 1457 - } 1458 - /// Build the final struct with custom extra_data 1459 - pub fn build_with_data( 1460 - self, 1461 - extra_data: std::collections::BTreeMap< 1462 - jacquard_common::smol_str::SmolStr, 1463 - jacquard_common::types::value::Data<'a>, 1464 - >, 1465 - ) -> Subfs<'a> { 1466 - Subfs { 1467 - flat: self.__unsafe_private_named.0, 1468 - subject: self.__unsafe_private_named.1.unwrap(), 1469 - r#type: self.__unsafe_private_named.2.unwrap(), 1470 - extra_data: Some(extra_data), 1471 - } 1472 - } 1473 - } 1474 - 1475 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> { 1476 - fn nsid() -> &'static str { 1477 - "place.wisp.fs" 1478 - } 1479 - fn def_name() -> &'static str { 1480 - "subfs" 1481 - } 1482 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1483 - lexicon_doc_place_wisp_fs() 1484 - } 1485 - fn validate( 1486 - &self, 1487 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1488 - Ok(()) 1489 - } 1490 - }
-653
rust-cli/crates/lexicons/src/place_wisp/settings.rs
··· 1 - // @generated by jacquard-lexicon. DO NOT EDIT. 2 - // 3 - // Lexicon: place.wisp.settings 4 - // 5 - // This file was automatically generated from Lexicon schemas. 6 - // Any manual changes will be overwritten on the next regeneration. 7 - 8 - /// Custom HTTP header configuration 9 - #[jacquard_derive::lexicon] 10 - #[derive( 11 - serde::Serialize, 12 - serde::Deserialize, 13 - Debug, 14 - Clone, 15 - PartialEq, 16 - Eq, 17 - jacquard_derive::IntoStatic, 18 - Default 19 - )] 20 - #[serde(rename_all = "camelCase")] 21 - pub struct CustomHeader<'a> { 22 - /// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options') 23 - #[serde(borrow)] 24 - pub name: jacquard_common::CowStr<'a>, 25 - /// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths. 26 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 27 - #[serde(borrow)] 28 - pub path: std::option::Option<jacquard_common::CowStr<'a>>, 29 - /// HTTP header value 30 - #[serde(borrow)] 31 - pub value: jacquard_common::CowStr<'a>, 32 - } 33 - 34 - fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc< 35 - 'static, 36 - > { 37 - ::jacquard_lexicon::lexicon::LexiconDoc { 38 - lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 39 - id: ::jacquard_common::CowStr::new_static("place.wisp.settings"), 40 - revision: None, 41 - description: None, 42 - defs: { 43 - let mut map = ::std::collections::BTreeMap::new(); 44 - map.insert( 45 - ::jacquard_common::smol_str::SmolStr::new_static("customHeader"), 46 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 47 - description: Some( 48 - ::jacquard_common::CowStr::new_static( 49 - "Custom HTTP header configuration", 50 - ), 51 - ), 52 - required: Some( 53 - vec![ 54 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 55 - ::jacquard_common::smol_str::SmolStr::new_static("value") 56 - ], 57 - ), 58 - nullable: None, 59 - properties: { 60 - #[allow(unused_mut)] 61 - let mut map = ::std::collections::BTreeMap::new(); 62 - map.insert( 63 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 64 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 65 - description: Some( 66 - ::jacquard_common::CowStr::new_static( 67 - "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')", 68 - ), 69 - ), 70 - format: None, 71 - default: None, 72 - min_length: None, 73 - max_length: Some(100usize), 74 - min_graphemes: None, 75 - max_graphemes: None, 76 - r#enum: None, 77 - r#const: None, 78 - known_values: None, 79 - }), 80 - ); 81 - map.insert( 82 - ::jacquard_common::smol_str::SmolStr::new_static("path"), 83 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 84 - description: Some( 85 - ::jacquard_common::CowStr::new_static( 86 - "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.", 87 - ), 88 - ), 89 - format: None, 90 - default: None, 91 - min_length: None, 92 - max_length: Some(500usize), 93 - min_graphemes: None, 94 - max_graphemes: None, 95 - r#enum: None, 96 - r#const: None, 97 - known_values: None, 98 - }), 99 - ); 100 - map.insert( 101 - ::jacquard_common::smol_str::SmolStr::new_static("value"), 102 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 103 - description: Some( 104 - ::jacquard_common::CowStr::new_static("HTTP header value"), 105 - ), 106 - format: None, 107 - default: None, 108 - min_length: None, 109 - max_length: Some(1000usize), 110 - min_graphemes: None, 111 - max_graphemes: None, 112 - r#enum: None, 113 - r#const: None, 114 - known_values: None, 115 - }), 116 - ); 117 - map 118 - }, 119 - }), 120 - ); 121 - map.insert( 122 - ::jacquard_common::smol_str::SmolStr::new_static("main"), 123 - ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 124 - description: Some( 125 - ::jacquard_common::CowStr::new_static( 126 - "Configuration settings for a static site hosted on wisp.place", 127 - ), 128 - ), 129 - key: Some(::jacquard_common::CowStr::new_static("any")), 130 - record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 131 - description: None, 132 - required: None, 133 - nullable: None, 134 - properties: { 135 - #[allow(unused_mut)] 136 - let mut map = ::std::collections::BTreeMap::new(); 137 - map.insert( 138 - ::jacquard_common::smol_str::SmolStr::new_static( 139 - "cleanUrls", 140 - ), 141 - ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 142 - description: None, 143 - default: None, 144 - r#const: None, 145 - }), 146 - ); 147 - map.insert( 148 - ::jacquard_common::smol_str::SmolStr::new_static( 149 - "custom404", 150 - ), 151 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 152 - description: Some( 153 - ::jacquard_common::CowStr::new_static( 154 - "Custom 404 error page file path. Incompatible with directoryListing and spaMode.", 155 - ), 156 - ), 157 - format: None, 158 - default: None, 159 - min_length: None, 160 - max_length: Some(500usize), 161 - min_graphemes: None, 162 - max_graphemes: None, 163 - r#enum: None, 164 - r#const: None, 165 - known_values: None, 166 - }), 167 - ); 168 - map.insert( 169 - ::jacquard_common::smol_str::SmolStr::new_static( 170 - "directoryListing", 171 - ), 172 - ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 173 - description: None, 174 - default: None, 175 - r#const: None, 176 - }), 177 - ); 178 - map.insert( 179 - ::jacquard_common::smol_str::SmolStr::new_static("headers"), 180 - ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 181 - description: Some( 182 - ::jacquard_common::CowStr::new_static( 183 - "Custom HTTP headers to set on responses", 184 - ), 185 - ), 186 - items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 187 - description: None, 188 - r#ref: ::jacquard_common::CowStr::new_static( 189 - "#customHeader", 190 - ), 191 - }), 192 - min_length: None, 193 - max_length: Some(50usize), 194 - }), 195 - ); 196 - map.insert( 197 - ::jacquard_common::smol_str::SmolStr::new_static( 198 - "indexFiles", 199 - ), 200 - ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 201 - description: Some( 202 - ::jacquard_common::CowStr::new_static( 203 - "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.", 204 - ), 205 - ), 206 - items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString { 207 - description: None, 208 - format: None, 209 - default: None, 210 - min_length: None, 211 - max_length: Some(255usize), 212 - min_graphemes: None, 213 - max_graphemes: None, 214 - r#enum: None, 215 - r#const: None, 216 - known_values: None, 217 - }), 218 - min_length: None, 219 - max_length: Some(10usize), 220 - }), 221 - ); 222 - map.insert( 223 - ::jacquard_common::smol_str::SmolStr::new_static("spaMode"), 224 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 225 - description: Some( 226 - ::jacquard_common::CowStr::new_static( 227 - "File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.", 228 - ), 229 - ), 230 - format: None, 231 - default: None, 232 - min_length: None, 233 - max_length: Some(500usize), 234 - min_graphemes: None, 235 - max_graphemes: None, 236 - r#enum: None, 237 - r#const: None, 238 - known_values: None, 239 - }), 240 - ); 241 - map 242 - }, 243 - }), 244 - }), 245 - ); 246 - map 247 - }, 248 - } 249 - } 250 - 251 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> { 252 - fn nsid() -> &'static str { 253 - "place.wisp.settings" 254 - } 255 - fn def_name() -> &'static str { 256 - "customHeader" 257 - } 258 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 259 - lexicon_doc_place_wisp_settings() 260 - } 261 - fn validate( 262 - &self, 263 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 264 - { 265 - let value = &self.name; 266 - #[allow(unused_comparisons)] 267 - if <str>::len(value.as_ref()) > 100usize { 268 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 269 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 270 - "name", 271 - ), 272 - max: 100usize, 273 - actual: <str>::len(value.as_ref()), 274 - }); 275 - } 276 - } 277 - if let Some(ref value) = self.path { 278 - #[allow(unused_comparisons)] 279 - if <str>::len(value.as_ref()) > 500usize { 280 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 281 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 282 - "path", 283 - ), 284 - max: 500usize, 285 - actual: <str>::len(value.as_ref()), 286 - }); 287 - } 288 - } 289 - { 290 - let value = &self.value; 291 - #[allow(unused_comparisons)] 292 - if <str>::len(value.as_ref()) > 1000usize { 293 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 294 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 295 - "value", 296 - ), 297 - max: 1000usize, 298 - actual: <str>::len(value.as_ref()), 299 - }); 300 - } 301 - } 302 - Ok(()) 303 - } 304 - } 305 - 306 - /// Configuration settings for a static site hosted on wisp.place 307 - #[jacquard_derive::lexicon] 308 - #[derive( 309 - serde::Serialize, 310 - serde::Deserialize, 311 - Debug, 312 - Clone, 313 - PartialEq, 314 - Eq, 315 - jacquard_derive::IntoStatic 316 - )] 317 - #[serde(rename_all = "camelCase")] 318 - pub struct Settings<'a> { 319 - /// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically. 320 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 321 - pub clean_urls: std::option::Option<bool>, 322 - /// Custom 404 error page file path. Incompatible with directoryListing and spaMode. 323 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 324 - #[serde(borrow)] 325 - pub custom404: std::option::Option<jacquard_common::CowStr<'a>>, 326 - /// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode. 327 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 328 - pub directory_listing: std::option::Option<bool>, 329 - /// Custom HTTP headers to set on responses 330 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 331 - #[serde(borrow)] 332 - pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>, 333 - /// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified. 334 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 335 - #[serde(borrow)] 336 - pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>, 337 - /// File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404. 338 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 339 - #[serde(borrow)] 340 - pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>, 341 - } 342 - 343 - pub mod settings_state { 344 - 345 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 346 - #[allow(unused)] 347 - use ::core::marker::PhantomData; 348 - mod sealed { 349 - pub trait Sealed {} 350 - } 351 - /// State trait tracking which required fields have been set 352 - pub trait State: sealed::Sealed {} 353 - /// Empty state - all required fields are unset 354 - pub struct Empty(()); 355 - impl sealed::Sealed for Empty {} 356 - impl State for Empty {} 357 - /// Marker types for field names 358 - #[allow(non_camel_case_types)] 359 - pub mod members {} 360 - } 361 - 362 - /// Builder for constructing an instance of this type 363 - pub struct SettingsBuilder<'a, S: settings_state::State> { 364 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 365 - __unsafe_private_named: ( 366 - ::core::option::Option<bool>, 367 - ::core::option::Option<jacquard_common::CowStr<'a>>, 368 - ::core::option::Option<bool>, 369 - ::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>, 370 - ::core::option::Option<Vec<jacquard_common::CowStr<'a>>>, 371 - ::core::option::Option<jacquard_common::CowStr<'a>>, 372 - ), 373 - _phantom: ::core::marker::PhantomData<&'a ()>, 374 - } 375 - 376 - impl<'a> Settings<'a> { 377 - /// Create a new builder for this type 378 - pub fn new() -> SettingsBuilder<'a, settings_state::Empty> { 379 - SettingsBuilder::new() 380 - } 381 - } 382 - 383 - impl<'a> SettingsBuilder<'a, settings_state::Empty> { 384 - /// Create a new builder with all fields unset 385 - pub fn new() -> Self { 386 - SettingsBuilder { 387 - _phantom_state: ::core::marker::PhantomData, 388 - __unsafe_private_named: (None, None, None, None, None, None), 389 - _phantom: ::core::marker::PhantomData, 390 - } 391 - } 392 - } 393 - 394 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 395 - /// Set the `cleanUrls` field (optional) 396 - pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self { 397 - self.__unsafe_private_named.0 = value.into(); 398 - self 399 - } 400 - /// Set the `cleanUrls` field to an Option value (optional) 401 - pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self { 402 - self.__unsafe_private_named.0 = value; 403 - self 404 - } 405 - } 406 - 407 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 408 - /// Set the `custom404` field (optional) 409 - pub fn custom404( 410 - mut self, 411 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 412 - ) -> Self { 413 - self.__unsafe_private_named.1 = value.into(); 414 - self 415 - } 416 - /// Set the `custom404` field to an Option value (optional) 417 - pub fn maybe_custom404( 418 - mut self, 419 - value: Option<jacquard_common::CowStr<'a>>, 420 - ) -> Self { 421 - self.__unsafe_private_named.1 = value; 422 - self 423 - } 424 - } 425 - 426 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 427 - /// Set the `directoryListing` field (optional) 428 - pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self { 429 - self.__unsafe_private_named.2 = value.into(); 430 - self 431 - } 432 - /// Set the `directoryListing` field to an Option value (optional) 433 - pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self { 434 - self.__unsafe_private_named.2 = value; 435 - self 436 - } 437 - } 438 - 439 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 440 - /// Set the `headers` field (optional) 441 - pub fn headers( 442 - mut self, 443 - value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>, 444 - ) -> Self { 445 - self.__unsafe_private_named.3 = value.into(); 446 - self 447 - } 448 - /// Set the `headers` field to an Option value (optional) 449 - pub fn maybe_headers( 450 - mut self, 451 - value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>, 452 - ) -> Self { 453 - self.__unsafe_private_named.3 = value; 454 - self 455 - } 456 - } 457 - 458 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 459 - /// Set the `indexFiles` field (optional) 460 - pub fn index_files( 461 - mut self, 462 - value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>, 463 - ) -> Self { 464 - self.__unsafe_private_named.4 = value.into(); 465 - self 466 - } 467 - /// Set the `indexFiles` field to an Option value (optional) 468 - pub fn maybe_index_files( 469 - mut self, 470 - value: Option<Vec<jacquard_common::CowStr<'a>>>, 471 - ) -> Self { 472 - self.__unsafe_private_named.4 = value; 473 - self 474 - } 475 - } 476 - 477 - impl<'a, S: settings_state::State> SettingsBuilder<'a, S> { 478 - /// Set the `spaMode` field (optional) 479 - pub fn spa_mode( 480 - mut self, 481 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 482 - ) -> Self { 483 - self.__unsafe_private_named.5 = value.into(); 484 - self 485 - } 486 - /// Set the `spaMode` field to an Option value (optional) 487 - pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 488 - self.__unsafe_private_named.5 = value; 489 - self 490 - } 491 - } 492 - 493 - impl<'a, S> SettingsBuilder<'a, S> 494 - where 495 - S: settings_state::State, 496 - { 497 - /// Build the final struct 498 - pub fn build(self) -> Settings<'a> { 499 - Settings { 500 - clean_urls: self.__unsafe_private_named.0, 501 - custom404: self.__unsafe_private_named.1, 502 - directory_listing: self.__unsafe_private_named.2, 503 - headers: self.__unsafe_private_named.3, 504 - index_files: self.__unsafe_private_named.4, 505 - spa_mode: self.__unsafe_private_named.5, 506 - extra_data: Default::default(), 507 - } 508 - } 509 - /// Build the final struct with custom extra_data 510 - pub fn build_with_data( 511 - self, 512 - extra_data: std::collections::BTreeMap< 513 - jacquard_common::smol_str::SmolStr, 514 - jacquard_common::types::value::Data<'a>, 515 - >, 516 - ) -> Settings<'a> { 517 - Settings { 518 - clean_urls: self.__unsafe_private_named.0, 519 - custom404: self.__unsafe_private_named.1, 520 - directory_listing: self.__unsafe_private_named.2, 521 - headers: self.__unsafe_private_named.3, 522 - index_files: self.__unsafe_private_named.4, 523 - spa_mode: self.__unsafe_private_named.5, 524 - extra_data: Some(extra_data), 525 - } 526 - } 527 - } 528 - 529 - impl<'a> Settings<'a> { 530 - pub fn uri( 531 - uri: impl Into<jacquard_common::CowStr<'a>>, 532 - ) -> Result< 533 - jacquard_common::types::uri::RecordUri<'a, SettingsRecord>, 534 - jacquard_common::types::uri::UriError, 535 - > { 536 - jacquard_common::types::uri::RecordUri::try_from_uri( 537 - jacquard_common::types::string::AtUri::new_cow(uri.into())?, 538 - ) 539 - } 540 - } 541 - 542 - /// Typed wrapper for GetRecord response with this collection's record type. 543 - #[derive( 544 - serde::Serialize, 545 - serde::Deserialize, 546 - Debug, 547 - Clone, 548 - PartialEq, 549 - Eq, 550 - jacquard_derive::IntoStatic 551 - )] 552 - #[serde(rename_all = "camelCase")] 553 - pub struct SettingsGetRecordOutput<'a> { 554 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 555 - #[serde(borrow)] 556 - pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 557 - #[serde(borrow)] 558 - pub uri: jacquard_common::types::string::AtUri<'a>, 559 - #[serde(borrow)] 560 - pub value: Settings<'a>, 561 - } 562 - 563 - impl From<SettingsGetRecordOutput<'_>> for Settings<'_> { 564 - fn from(output: SettingsGetRecordOutput<'_>) -> Self { 565 - use jacquard_common::IntoStatic; 566 - output.value.into_static() 567 - } 568 - } 569 - 570 - impl jacquard_common::types::collection::Collection for Settings<'_> { 571 - const NSID: &'static str = "place.wisp.settings"; 572 - type Record = SettingsRecord; 573 - } 574 - 575 - /// Marker type for deserializing records from this collection. 576 - #[derive(Debug, serde::Serialize, serde::Deserialize)] 577 - pub struct SettingsRecord; 578 - impl jacquard_common::xrpc::XrpcResp for SettingsRecord { 579 - const NSID: &'static str = "place.wisp.settings"; 580 - const ENCODING: &'static str = "application/json"; 581 - type Output<'de> = SettingsGetRecordOutput<'de>; 582 - type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 583 - } 584 - 585 - impl jacquard_common::types::collection::Collection for SettingsRecord { 586 - const NSID: &'static str = "place.wisp.settings"; 587 - type Record = SettingsRecord; 588 - } 589 - 590 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> { 591 - fn nsid() -> &'static str { 592 - "place.wisp.settings" 593 - } 594 - fn def_name() -> &'static str { 595 - "main" 596 - } 597 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 598 - lexicon_doc_place_wisp_settings() 599 - } 600 - fn validate( 601 - &self, 602 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 603 - if let Some(ref value) = self.custom404 { 604 - #[allow(unused_comparisons)] 605 - if <str>::len(value.as_ref()) > 500usize { 606 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 607 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 608 - "custom404", 609 - ), 610 - max: 500usize, 611 - actual: <str>::len(value.as_ref()), 612 - }); 613 - } 614 - } 615 - if let Some(ref value) = self.headers { 616 - #[allow(unused_comparisons)] 617 - if value.len() > 50usize { 618 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 619 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 620 - "headers", 621 - ), 622 - max: 50usize, 623 - actual: value.len(), 624 - }); 625 - } 626 - } 627 - if let Some(ref value) = self.index_files { 628 - #[allow(unused_comparisons)] 629 - if value.len() > 10usize { 630 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 631 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 632 - "index_files", 633 - ), 634 - max: 10usize, 635 - actual: value.len(), 636 - }); 637 - } 638 - } 639 - if let Some(ref value) = self.spa_mode { 640 - #[allow(unused_comparisons)] 641 - if <str>::len(value.as_ref()) > 500usize { 642 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 643 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 644 - "spa_mode", 645 - ), 646 - max: 500usize, 647 - actual: <str>::len(value.as_ref()), 648 - }); 649 - } 650 - } 651 - Ok(()) 652 - } 653 - }
-1408
rust-cli/crates/lexicons/src/place_wisp/subfs.rs
··· 1 - // @generated by jacquard-lexicon. DO NOT EDIT. 2 - // 3 - // Lexicon: place.wisp.subfs 4 - // 5 - // This file was automatically generated from Lexicon schemas. 6 - // Any manual changes will be overwritten on the next regeneration. 7 - 8 - #[jacquard_derive::lexicon] 9 - #[derive( 10 - serde::Serialize, 11 - serde::Deserialize, 12 - Debug, 13 - Clone, 14 - PartialEq, 15 - Eq, 16 - jacquard_derive::IntoStatic 17 - )] 18 - #[serde(rename_all = "camelCase")] 19 - pub struct Directory<'a> { 20 - #[serde(borrow)] 21 - pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>, 22 - #[serde(borrow)] 23 - pub r#type: jacquard_common::CowStr<'a>, 24 - } 25 - 26 - pub mod directory_state { 27 - 28 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 29 - #[allow(unused)] 30 - use ::core::marker::PhantomData; 31 - mod sealed { 32 - pub trait Sealed {} 33 - } 34 - /// State trait tracking which required fields have been set 35 - pub trait State: sealed::Sealed { 36 - type Type; 37 - type Entries; 38 - } 39 - /// Empty state - all required fields are unset 40 - pub struct Empty(()); 41 - impl sealed::Sealed for Empty {} 42 - impl State for Empty { 43 - type Type = Unset; 44 - type Entries = Unset; 45 - } 46 - ///State transition - sets the `type` field to Set 47 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 48 - impl<S: State> sealed::Sealed for SetType<S> {} 49 - impl<S: State> State for SetType<S> { 50 - type Type = Set<members::r#type>; 51 - type Entries = S::Entries; 52 - } 53 - ///State transition - sets the `entries` field to Set 54 - pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>); 55 - impl<S: State> sealed::Sealed for SetEntries<S> {} 56 - impl<S: State> State for SetEntries<S> { 57 - type Type = S::Type; 58 - type Entries = Set<members::entries>; 59 - } 60 - /// Marker types for field names 61 - #[allow(non_camel_case_types)] 62 - pub mod members { 63 - ///Marker type for the `type` field 64 - pub struct r#type(()); 65 - ///Marker type for the `entries` field 66 - pub struct entries(()); 67 - } 68 - } 69 - 70 - /// Builder for constructing an instance of this type 71 - pub struct DirectoryBuilder<'a, S: directory_state::State> { 72 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 73 - __unsafe_private_named: ( 74 - ::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>, 75 - ::core::option::Option<jacquard_common::CowStr<'a>>, 76 - ), 77 - _phantom: ::core::marker::PhantomData<&'a ()>, 78 - } 79 - 80 - impl<'a> Directory<'a> { 81 - /// Create a new builder for this type 82 - pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> { 83 - DirectoryBuilder::new() 84 - } 85 - } 86 - 87 - impl<'a> DirectoryBuilder<'a, directory_state::Empty> { 88 - /// Create a new builder with all fields unset 89 - pub fn new() -> Self { 90 - DirectoryBuilder { 91 - _phantom_state: ::core::marker::PhantomData, 92 - __unsafe_private_named: (None, None), 93 - _phantom: ::core::marker::PhantomData, 94 - } 95 - } 96 - } 97 - 98 - impl<'a, S> DirectoryBuilder<'a, S> 99 - where 100 - S: directory_state::State, 101 - S::Entries: directory_state::IsUnset, 102 - { 103 - /// Set the `entries` field (required) 104 - pub fn entries( 105 - mut self, 106 - value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>, 107 - ) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> { 108 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 109 - DirectoryBuilder { 110 - _phantom_state: ::core::marker::PhantomData, 111 - __unsafe_private_named: self.__unsafe_private_named, 112 - _phantom: ::core::marker::PhantomData, 113 - } 114 - } 115 - } 116 - 117 - impl<'a, S> DirectoryBuilder<'a, S> 118 - where 119 - S: directory_state::State, 120 - S::Type: directory_state::IsUnset, 121 - { 122 - /// Set the `type` field (required) 123 - pub fn r#type( 124 - mut self, 125 - value: impl Into<jacquard_common::CowStr<'a>>, 126 - ) -> DirectoryBuilder<'a, directory_state::SetType<S>> { 127 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 128 - DirectoryBuilder { 129 - _phantom_state: ::core::marker::PhantomData, 130 - __unsafe_private_named: self.__unsafe_private_named, 131 - _phantom: ::core::marker::PhantomData, 132 - } 133 - } 134 - } 135 - 136 - impl<'a, S> DirectoryBuilder<'a, S> 137 - where 138 - S: directory_state::State, 139 - S::Type: directory_state::IsSet, 140 - S::Entries: directory_state::IsSet, 141 - { 142 - /// Build the final struct 143 - pub fn build(self) -> Directory<'a> { 144 - Directory { 145 - entries: self.__unsafe_private_named.0.unwrap(), 146 - r#type: self.__unsafe_private_named.1.unwrap(), 147 - extra_data: Default::default(), 148 - } 149 - } 150 - /// Build the final struct with custom extra_data 151 - pub fn build_with_data( 152 - self, 153 - extra_data: std::collections::BTreeMap< 154 - jacquard_common::smol_str::SmolStr, 155 - jacquard_common::types::value::Data<'a>, 156 - >, 157 - ) -> Directory<'a> { 158 - Directory { 159 - entries: self.__unsafe_private_named.0.unwrap(), 160 - r#type: self.__unsafe_private_named.1.unwrap(), 161 - extra_data: Some(extra_data), 162 - } 163 - } 164 - } 165 - 166 - fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 167 - ::jacquard_lexicon::lexicon::LexiconDoc { 168 - lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1, 169 - id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"), 170 - revision: None, 171 - description: None, 172 - defs: { 173 - let mut map = ::std::collections::BTreeMap::new(); 174 - map.insert( 175 - ::jacquard_common::smol_str::SmolStr::new_static("directory"), 176 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 177 - description: None, 178 - required: Some( 179 - vec![ 180 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 181 - ::jacquard_common::smol_str::SmolStr::new_static("entries") 182 - ], 183 - ), 184 - nullable: None, 185 - properties: { 186 - #[allow(unused_mut)] 187 - let mut map = ::std::collections::BTreeMap::new(); 188 - map.insert( 189 - ::jacquard_common::smol_str::SmolStr::new_static("entries"), 190 - ::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray { 191 - description: None, 192 - items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef { 193 - description: None, 194 - r#ref: ::jacquard_common::CowStr::new_static("#entry"), 195 - }), 196 - min_length: None, 197 - max_length: Some(500usize), 198 - }), 199 - ); 200 - map.insert( 201 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 202 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 203 - description: None, 204 - format: None, 205 - default: None, 206 - min_length: None, 207 - max_length: None, 208 - min_graphemes: None, 209 - max_graphemes: None, 210 - r#enum: None, 211 - r#const: None, 212 - known_values: None, 213 - }), 214 - ); 215 - map 216 - }, 217 - }), 218 - ); 219 - map.insert( 220 - ::jacquard_common::smol_str::SmolStr::new_static("entry"), 221 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 222 - description: None, 223 - required: Some( 224 - vec![ 225 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 226 - ::jacquard_common::smol_str::SmolStr::new_static("node") 227 - ], 228 - ), 229 - nullable: None, 230 - properties: { 231 - #[allow(unused_mut)] 232 - let mut map = ::std::collections::BTreeMap::new(); 233 - map.insert( 234 - ::jacquard_common::smol_str::SmolStr::new_static("name"), 235 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 236 - description: None, 237 - format: None, 238 - default: None, 239 - min_length: None, 240 - max_length: Some(255usize), 241 - min_graphemes: None, 242 - max_graphemes: None, 243 - r#enum: None, 244 - r#const: None, 245 - known_values: None, 246 - }), 247 - ); 248 - map.insert( 249 - ::jacquard_common::smol_str::SmolStr::new_static("node"), 250 - ::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion { 251 - description: None, 252 - refs: vec![ 253 - ::jacquard_common::CowStr::new_static("#file"), 254 - ::jacquard_common::CowStr::new_static("#directory"), 255 - ::jacquard_common::CowStr::new_static("#subfs") 256 - ], 257 - closed: None, 258 - }), 259 - ); 260 - map 261 - }, 262 - }), 263 - ); 264 - map.insert( 265 - ::jacquard_common::smol_str::SmolStr::new_static("file"), 266 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 267 - description: None, 268 - required: Some( 269 - vec![ 270 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 271 - ::jacquard_common::smol_str::SmolStr::new_static("blob") 272 - ], 273 - ), 274 - nullable: None, 275 - properties: { 276 - #[allow(unused_mut)] 277 - let mut map = ::std::collections::BTreeMap::new(); 278 - map.insert( 279 - ::jacquard_common::smol_str::SmolStr::new_static("base64"), 280 - ::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean { 281 - description: None, 282 - default: None, 283 - r#const: None, 284 - }), 285 - ); 286 - map.insert( 287 - ::jacquard_common::smol_str::SmolStr::new_static("blob"), 288 - ::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob { 289 - description: None, 290 - accept: None, 291 - max_size: None, 292 - }), 293 - ); 294 - map.insert( 295 - ::jacquard_common::smol_str::SmolStr::new_static("encoding"), 296 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 297 - description: Some( 298 - ::jacquard_common::CowStr::new_static( 299 - "Content encoding (e.g., gzip for compressed files)", 300 - ), 301 - ), 302 - format: None, 303 - default: None, 304 - min_length: None, 305 - max_length: None, 306 - min_graphemes: None, 307 - max_graphemes: None, 308 - r#enum: None, 309 - r#const: None, 310 - known_values: None, 311 - }), 312 - ); 313 - map.insert( 314 - ::jacquard_common::smol_str::SmolStr::new_static("mimeType"), 315 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 316 - description: Some( 317 - ::jacquard_common::CowStr::new_static( 318 - "Original MIME type before compression", 319 - ), 320 - ), 321 - format: None, 322 - default: None, 323 - min_length: None, 324 - max_length: None, 325 - min_graphemes: None, 326 - max_graphemes: None, 327 - r#enum: None, 328 - r#const: None, 329 - known_values: None, 330 - }), 331 - ); 332 - map.insert( 333 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 334 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 335 - description: None, 336 - format: None, 337 - default: None, 338 - min_length: None, 339 - max_length: None, 340 - min_graphemes: None, 341 - max_graphemes: None, 342 - r#enum: None, 343 - r#const: None, 344 - known_values: None, 345 - }), 346 - ); 347 - map 348 - }, 349 - }), 350 - ); 351 - map.insert( 352 - ::jacquard_common::smol_str::SmolStr::new_static("main"), 353 - ::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord { 354 - description: Some( 355 - ::jacquard_common::CowStr::new_static( 356 - "Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.", 357 - ), 358 - ), 359 - key: None, 360 - record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject { 361 - description: None, 362 - required: Some( 363 - vec![ 364 - ::jacquard_common::smol_str::SmolStr::new_static("root"), 365 - ::jacquard_common::smol_str::SmolStr::new_static("createdAt") 366 - ], 367 - ), 368 - nullable: None, 369 - properties: { 370 - #[allow(unused_mut)] 371 - let mut map = ::std::collections::BTreeMap::new(); 372 - map.insert( 373 - ::jacquard_common::smol_str::SmolStr::new_static( 374 - "createdAt", 375 - ), 376 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 377 - description: None, 378 - format: Some( 379 - ::jacquard_lexicon::lexicon::LexStringFormat::Datetime, 380 - ), 381 - default: None, 382 - min_length: None, 383 - max_length: None, 384 - min_graphemes: None, 385 - max_graphemes: None, 386 - r#enum: None, 387 - r#const: None, 388 - known_values: None, 389 - }), 390 - ); 391 - map.insert( 392 - ::jacquard_common::smol_str::SmolStr::new_static( 393 - "fileCount", 394 - ), 395 - ::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger { 396 - description: None, 397 - default: None, 398 - minimum: Some(0i64), 399 - maximum: Some(1000i64), 400 - r#enum: None, 401 - r#const: None, 402 - }), 403 - ); 404 - map.insert( 405 - ::jacquard_common::smol_str::SmolStr::new_static("root"), 406 - ::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef { 407 - description: None, 408 - r#ref: ::jacquard_common::CowStr::new_static("#directory"), 409 - }), 410 - ); 411 - map 412 - }, 413 - }), 414 - }), 415 - ); 416 - map.insert( 417 - ::jacquard_common::smol_str::SmolStr::new_static("subfs"), 418 - ::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject { 419 - description: None, 420 - required: Some( 421 - vec![ 422 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 423 - ::jacquard_common::smol_str::SmolStr::new_static("subject") 424 - ], 425 - ), 426 - nullable: None, 427 - properties: { 428 - #[allow(unused_mut)] 429 - let mut map = ::std::collections::BTreeMap::new(); 430 - map.insert( 431 - ::jacquard_common::smol_str::SmolStr::new_static("subject"), 432 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 433 - description: Some( 434 - ::jacquard_common::CowStr::new_static( 435 - "AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.", 436 - ), 437 - ), 438 - format: Some( 439 - ::jacquard_lexicon::lexicon::LexStringFormat::AtUri, 440 - ), 441 - default: None, 442 - min_length: None, 443 - max_length: None, 444 - min_graphemes: None, 445 - max_graphemes: None, 446 - r#enum: None, 447 - r#const: None, 448 - known_values: None, 449 - }), 450 - ); 451 - map.insert( 452 - ::jacquard_common::smol_str::SmolStr::new_static("type"), 453 - ::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString { 454 - description: None, 455 - format: None, 456 - default: None, 457 - min_length: None, 458 - max_length: None, 459 - min_graphemes: None, 460 - max_graphemes: None, 461 - r#enum: None, 462 - r#const: None, 463 - known_values: None, 464 - }), 465 - ); 466 - map 467 - }, 468 - }), 469 - ); 470 - map 471 - }, 472 - } 473 - } 474 - 475 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> { 476 - fn nsid() -> &'static str { 477 - "place.wisp.subfs" 478 - } 479 - fn def_name() -> &'static str { 480 - "directory" 481 - } 482 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 483 - lexicon_doc_place_wisp_subfs() 484 - } 485 - fn validate( 486 - &self, 487 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 488 - { 489 - let value = &self.entries; 490 - #[allow(unused_comparisons)] 491 - if value.len() > 500usize { 492 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 493 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 494 - "entries", 495 - ), 496 - max: 500usize, 497 - actual: value.len(), 498 - }); 499 - } 500 - } 501 - Ok(()) 502 - } 503 - } 504 - 505 - #[jacquard_derive::lexicon] 506 - #[derive( 507 - serde::Serialize, 508 - serde::Deserialize, 509 - Debug, 510 - Clone, 511 - PartialEq, 512 - Eq, 513 - jacquard_derive::IntoStatic 514 - )] 515 - #[serde(rename_all = "camelCase")] 516 - pub struct Entry<'a> { 517 - #[serde(borrow)] 518 - pub name: jacquard_common::CowStr<'a>, 519 - #[serde(borrow)] 520 - pub node: EntryNode<'a>, 521 - } 522 - 523 - pub mod entry_state { 524 - 525 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 526 - #[allow(unused)] 527 - use ::core::marker::PhantomData; 528 - mod sealed { 529 - pub trait Sealed {} 530 - } 531 - /// State trait tracking which required fields have been set 532 - pub trait State: sealed::Sealed { 533 - type Name; 534 - type Node; 535 - } 536 - /// Empty state - all required fields are unset 537 - pub struct Empty(()); 538 - impl sealed::Sealed for Empty {} 539 - impl State for Empty { 540 - type Name = Unset; 541 - type Node = Unset; 542 - } 543 - ///State transition - sets the `name` field to Set 544 - pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>); 545 - impl<S: State> sealed::Sealed for SetName<S> {} 546 - impl<S: State> State for SetName<S> { 547 - type Name = Set<members::name>; 548 - type Node = S::Node; 549 - } 550 - ///State transition - sets the `node` field to Set 551 - pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>); 552 - impl<S: State> sealed::Sealed for SetNode<S> {} 553 - impl<S: State> State for SetNode<S> { 554 - type Name = S::Name; 555 - type Node = Set<members::node>; 556 - } 557 - /// Marker types for field names 558 - #[allow(non_camel_case_types)] 559 - pub mod members { 560 - ///Marker type for the `name` field 561 - pub struct name(()); 562 - ///Marker type for the `node` field 563 - pub struct node(()); 564 - } 565 - } 566 - 567 - /// Builder for constructing an instance of this type 568 - pub struct EntryBuilder<'a, S: entry_state::State> { 569 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 570 - __unsafe_private_named: ( 571 - ::core::option::Option<jacquard_common::CowStr<'a>>, 572 - ::core::option::Option<EntryNode<'a>>, 573 - ), 574 - _phantom: ::core::marker::PhantomData<&'a ()>, 575 - } 576 - 577 - impl<'a> Entry<'a> { 578 - /// Create a new builder for this type 579 - pub fn new() -> EntryBuilder<'a, entry_state::Empty> { 580 - EntryBuilder::new() 581 - } 582 - } 583 - 584 - impl<'a> EntryBuilder<'a, entry_state::Empty> { 585 - /// Create a new builder with all fields unset 586 - pub fn new() -> Self { 587 - EntryBuilder { 588 - _phantom_state: ::core::marker::PhantomData, 589 - __unsafe_private_named: (None, None), 590 - _phantom: ::core::marker::PhantomData, 591 - } 592 - } 593 - } 594 - 595 - impl<'a, S> EntryBuilder<'a, S> 596 - where 597 - S: entry_state::State, 598 - S::Name: entry_state::IsUnset, 599 - { 600 - /// Set the `name` field (required) 601 - pub fn name( 602 - mut self, 603 - value: impl Into<jacquard_common::CowStr<'a>>, 604 - ) -> EntryBuilder<'a, entry_state::SetName<S>> { 605 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 606 - EntryBuilder { 607 - _phantom_state: ::core::marker::PhantomData, 608 - __unsafe_private_named: self.__unsafe_private_named, 609 - _phantom: ::core::marker::PhantomData, 610 - } 611 - } 612 - } 613 - 614 - impl<'a, S> EntryBuilder<'a, S> 615 - where 616 - S: entry_state::State, 617 - S::Node: entry_state::IsUnset, 618 - { 619 - /// Set the `node` field (required) 620 - pub fn node( 621 - mut self, 622 - value: impl Into<EntryNode<'a>>, 623 - ) -> EntryBuilder<'a, entry_state::SetNode<S>> { 624 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 625 - EntryBuilder { 626 - _phantom_state: ::core::marker::PhantomData, 627 - __unsafe_private_named: self.__unsafe_private_named, 628 - _phantom: ::core::marker::PhantomData, 629 - } 630 - } 631 - } 632 - 633 - impl<'a, S> EntryBuilder<'a, S> 634 - where 635 - S: entry_state::State, 636 - S::Name: entry_state::IsSet, 637 - S::Node: entry_state::IsSet, 638 - { 639 - /// Build the final struct 640 - pub fn build(self) -> Entry<'a> { 641 - Entry { 642 - name: self.__unsafe_private_named.0.unwrap(), 643 - node: self.__unsafe_private_named.1.unwrap(), 644 - extra_data: Default::default(), 645 - } 646 - } 647 - /// Build the final struct with custom extra_data 648 - pub fn build_with_data( 649 - self, 650 - extra_data: std::collections::BTreeMap< 651 - jacquard_common::smol_str::SmolStr, 652 - jacquard_common::types::value::Data<'a>, 653 - >, 654 - ) -> Entry<'a> { 655 - Entry { 656 - name: self.__unsafe_private_named.0.unwrap(), 657 - node: self.__unsafe_private_named.1.unwrap(), 658 - extra_data: Some(extra_data), 659 - } 660 - } 661 - } 662 - 663 - #[jacquard_derive::open_union] 664 - #[derive( 665 - serde::Serialize, 666 - serde::Deserialize, 667 - Debug, 668 - Clone, 669 - PartialEq, 670 - Eq, 671 - jacquard_derive::IntoStatic 672 - )] 673 - #[serde(tag = "$type")] 674 - #[serde(bound(deserialize = "'de: 'a"))] 675 - pub enum EntryNode<'a> { 676 - #[serde(rename = "place.wisp.subfs#file")] 677 - File(Box<crate::place_wisp::subfs::File<'a>>), 678 - #[serde(rename = "place.wisp.subfs#directory")] 679 - Directory(Box<crate::place_wisp::subfs::Directory<'a>>), 680 - #[serde(rename = "place.wisp.subfs#subfs")] 681 - Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>), 682 - } 683 - 684 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> { 685 - fn nsid() -> &'static str { 686 - "place.wisp.subfs" 687 - } 688 - fn def_name() -> &'static str { 689 - "entry" 690 - } 691 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 692 - lexicon_doc_place_wisp_subfs() 693 - } 694 - fn validate( 695 - &self, 696 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 697 - { 698 - let value = &self.name; 699 - #[allow(unused_comparisons)] 700 - if <str>::len(value.as_ref()) > 255usize { 701 - return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength { 702 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 703 - "name", 704 - ), 705 - max: 255usize, 706 - actual: <str>::len(value.as_ref()), 707 - }); 708 - } 709 - } 710 - Ok(()) 711 - } 712 - } 713 - 714 - #[jacquard_derive::lexicon] 715 - #[derive( 716 - serde::Serialize, 717 - serde::Deserialize, 718 - Debug, 719 - Clone, 720 - PartialEq, 721 - Eq, 722 - jacquard_derive::IntoStatic 723 - )] 724 - #[serde(rename_all = "camelCase")] 725 - pub struct File<'a> { 726 - /// True if blob content is base64-encoded (used to bypass PDS content sniffing) 727 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 728 - pub base64: std::option::Option<bool>, 729 - /// Content blob ref 730 - #[serde(borrow)] 731 - pub blob: jacquard_common::types::blob::BlobRef<'a>, 732 - /// Content encoding (e.g., gzip for compressed files) 733 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 734 - #[serde(borrow)] 735 - pub encoding: std::option::Option<jacquard_common::CowStr<'a>>, 736 - /// Original MIME type before compression 737 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 738 - #[serde(borrow)] 739 - pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>, 740 - #[serde(borrow)] 741 - pub r#type: jacquard_common::CowStr<'a>, 742 - } 743 - 744 - pub mod file_state { 745 - 746 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 747 - #[allow(unused)] 748 - use ::core::marker::PhantomData; 749 - mod sealed { 750 - pub trait Sealed {} 751 - } 752 - /// State trait tracking which required fields have been set 753 - pub trait State: sealed::Sealed { 754 - type Blob; 755 - type Type; 756 - } 757 - /// Empty state - all required fields are unset 758 - pub struct Empty(()); 759 - impl sealed::Sealed for Empty {} 760 - impl State for Empty { 761 - type Blob = Unset; 762 - type Type = Unset; 763 - } 764 - ///State transition - sets the `blob` field to Set 765 - pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>); 766 - impl<S: State> sealed::Sealed for SetBlob<S> {} 767 - impl<S: State> State for SetBlob<S> { 768 - type Blob = Set<members::blob>; 769 - type Type = S::Type; 770 - } 771 - ///State transition - sets the `type` field to Set 772 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 773 - impl<S: State> sealed::Sealed for SetType<S> {} 774 - impl<S: State> State for SetType<S> { 775 - type Blob = S::Blob; 776 - type Type = Set<members::r#type>; 777 - } 778 - /// Marker types for field names 779 - #[allow(non_camel_case_types)] 780 - pub mod members { 781 - ///Marker type for the `blob` field 782 - pub struct blob(()); 783 - ///Marker type for the `type` field 784 - pub struct r#type(()); 785 - } 786 - } 787 - 788 - /// Builder for constructing an instance of this type 789 - pub struct FileBuilder<'a, S: file_state::State> { 790 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 791 - __unsafe_private_named: ( 792 - ::core::option::Option<bool>, 793 - ::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>, 794 - ::core::option::Option<jacquard_common::CowStr<'a>>, 795 - ::core::option::Option<jacquard_common::CowStr<'a>>, 796 - ::core::option::Option<jacquard_common::CowStr<'a>>, 797 - ), 798 - _phantom: ::core::marker::PhantomData<&'a ()>, 799 - } 800 - 801 - impl<'a> File<'a> { 802 - /// Create a new builder for this type 803 - pub fn new() -> FileBuilder<'a, file_state::Empty> { 804 - FileBuilder::new() 805 - } 806 - } 807 - 808 - impl<'a> FileBuilder<'a, file_state::Empty> { 809 - /// Create a new builder with all fields unset 810 - pub fn new() -> Self { 811 - FileBuilder { 812 - _phantom_state: ::core::marker::PhantomData, 813 - __unsafe_private_named: (None, None, None, None, None), 814 - _phantom: ::core::marker::PhantomData, 815 - } 816 - } 817 - } 818 - 819 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 820 - /// Set the `base64` field (optional) 821 - pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self { 822 - self.__unsafe_private_named.0 = value.into(); 823 - self 824 - } 825 - /// Set the `base64` field to an Option value (optional) 826 - pub fn maybe_base64(mut self, value: Option<bool>) -> Self { 827 - self.__unsafe_private_named.0 = value; 828 - self 829 - } 830 - } 831 - 832 - impl<'a, S> FileBuilder<'a, S> 833 - where 834 - S: file_state::State, 835 - S::Blob: file_state::IsUnset, 836 - { 837 - /// Set the `blob` field (required) 838 - pub fn blob( 839 - mut self, 840 - value: impl Into<jacquard_common::types::blob::BlobRef<'a>>, 841 - ) -> FileBuilder<'a, file_state::SetBlob<S>> { 842 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 843 - FileBuilder { 844 - _phantom_state: ::core::marker::PhantomData, 845 - __unsafe_private_named: self.__unsafe_private_named, 846 - _phantom: ::core::marker::PhantomData, 847 - } 848 - } 849 - } 850 - 851 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 852 - /// Set the `encoding` field (optional) 853 - pub fn encoding( 854 - mut self, 855 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 856 - ) -> Self { 857 - self.__unsafe_private_named.2 = value.into(); 858 - self 859 - } 860 - /// Set the `encoding` field to an Option value (optional) 861 - pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self { 862 - self.__unsafe_private_named.2 = value; 863 - self 864 - } 865 - } 866 - 867 - impl<'a, S: file_state::State> FileBuilder<'a, S> { 868 - /// Set the `mimeType` field (optional) 869 - pub fn mime_type( 870 - mut self, 871 - value: impl Into<Option<jacquard_common::CowStr<'a>>>, 872 - ) -> Self { 873 - self.__unsafe_private_named.3 = value.into(); 874 - self 875 - } 876 - /// Set the `mimeType` field to an Option value (optional) 877 - pub fn maybe_mime_type( 878 - mut self, 879 - value: Option<jacquard_common::CowStr<'a>>, 880 - ) -> Self { 881 - self.__unsafe_private_named.3 = value; 882 - self 883 - } 884 - } 885 - 886 - impl<'a, S> FileBuilder<'a, S> 887 - where 888 - S: file_state::State, 889 - S::Type: file_state::IsUnset, 890 - { 891 - /// Set the `type` field (required) 892 - pub fn r#type( 893 - mut self, 894 - value: impl Into<jacquard_common::CowStr<'a>>, 895 - ) -> FileBuilder<'a, file_state::SetType<S>> { 896 - self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into()); 897 - FileBuilder { 898 - _phantom_state: ::core::marker::PhantomData, 899 - __unsafe_private_named: self.__unsafe_private_named, 900 - _phantom: ::core::marker::PhantomData, 901 - } 902 - } 903 - } 904 - 905 - impl<'a, S> FileBuilder<'a, S> 906 - where 907 - S: file_state::State, 908 - S::Blob: file_state::IsSet, 909 - S::Type: file_state::IsSet, 910 - { 911 - /// Build the final struct 912 - pub fn build(self) -> File<'a> { 913 - File { 914 - base64: self.__unsafe_private_named.0, 915 - blob: self.__unsafe_private_named.1.unwrap(), 916 - encoding: self.__unsafe_private_named.2, 917 - mime_type: self.__unsafe_private_named.3, 918 - r#type: self.__unsafe_private_named.4.unwrap(), 919 - extra_data: Default::default(), 920 - } 921 - } 922 - /// Build the final struct with custom extra_data 923 - pub fn build_with_data( 924 - self, 925 - extra_data: std::collections::BTreeMap< 926 - jacquard_common::smol_str::SmolStr, 927 - jacquard_common::types::value::Data<'a>, 928 - >, 929 - ) -> File<'a> { 930 - File { 931 - base64: self.__unsafe_private_named.0, 932 - blob: self.__unsafe_private_named.1.unwrap(), 933 - encoding: self.__unsafe_private_named.2, 934 - mime_type: self.__unsafe_private_named.3, 935 - r#type: self.__unsafe_private_named.4.unwrap(), 936 - extra_data: Some(extra_data), 937 - } 938 - } 939 - } 940 - 941 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> { 942 - fn nsid() -> &'static str { 943 - "place.wisp.subfs" 944 - } 945 - fn def_name() -> &'static str { 946 - "file" 947 - } 948 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 949 - lexicon_doc_place_wisp_subfs() 950 - } 951 - fn validate( 952 - &self, 953 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 954 - Ok(()) 955 - } 956 - } 957 - 958 - /// Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure. 959 - #[jacquard_derive::lexicon] 960 - #[derive( 961 - serde::Serialize, 962 - serde::Deserialize, 963 - Debug, 964 - Clone, 965 - PartialEq, 966 - Eq, 967 - jacquard_derive::IntoStatic 968 - )] 969 - #[serde(rename_all = "camelCase")] 970 - pub struct SubfsRecord<'a> { 971 - pub created_at: jacquard_common::types::string::Datetime, 972 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 973 - pub file_count: std::option::Option<i64>, 974 - #[serde(borrow)] 975 - pub root: crate::place_wisp::subfs::Directory<'a>, 976 - } 977 - 978 - pub mod subfs_record_state { 979 - 980 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 981 - #[allow(unused)] 982 - use ::core::marker::PhantomData; 983 - mod sealed { 984 - pub trait Sealed {} 985 - } 986 - /// State trait tracking which required fields have been set 987 - pub trait State: sealed::Sealed { 988 - type Root; 989 - type CreatedAt; 990 - } 991 - /// Empty state - all required fields are unset 992 - pub struct Empty(()); 993 - impl sealed::Sealed for Empty {} 994 - impl State for Empty { 995 - type Root = Unset; 996 - type CreatedAt = Unset; 997 - } 998 - ///State transition - sets the `root` field to Set 999 - pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>); 1000 - impl<S: State> sealed::Sealed for SetRoot<S> {} 1001 - impl<S: State> State for SetRoot<S> { 1002 - type Root = Set<members::root>; 1003 - type CreatedAt = S::CreatedAt; 1004 - } 1005 - ///State transition - sets the `created_at` field to Set 1006 - pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>); 1007 - impl<S: State> sealed::Sealed for SetCreatedAt<S> {} 1008 - impl<S: State> State for SetCreatedAt<S> { 1009 - type Root = S::Root; 1010 - type CreatedAt = Set<members::created_at>; 1011 - } 1012 - /// Marker types for field names 1013 - #[allow(non_camel_case_types)] 1014 - pub mod members { 1015 - ///Marker type for the `root` field 1016 - pub struct root(()); 1017 - ///Marker type for the `created_at` field 1018 - pub struct created_at(()); 1019 - } 1020 - } 1021 - 1022 - /// Builder for constructing an instance of this type 1023 - pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> { 1024 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1025 - __unsafe_private_named: ( 1026 - ::core::option::Option<jacquard_common::types::string::Datetime>, 1027 - ::core::option::Option<i64>, 1028 - ::core::option::Option<crate::place_wisp::subfs::Directory<'a>>, 1029 - ), 1030 - _phantom: ::core::marker::PhantomData<&'a ()>, 1031 - } 1032 - 1033 - impl<'a> SubfsRecord<'a> { 1034 - /// Create a new builder for this type 1035 - pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> { 1036 - SubfsRecordBuilder::new() 1037 - } 1038 - } 1039 - 1040 - impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> { 1041 - /// Create a new builder with all fields unset 1042 - pub fn new() -> Self { 1043 - SubfsRecordBuilder { 1044 - _phantom_state: ::core::marker::PhantomData, 1045 - __unsafe_private_named: (None, None, None), 1046 - _phantom: ::core::marker::PhantomData, 1047 - } 1048 - } 1049 - } 1050 - 1051 - impl<'a, S> SubfsRecordBuilder<'a, S> 1052 - where 1053 - S: subfs_record_state::State, 1054 - S::CreatedAt: subfs_record_state::IsUnset, 1055 - { 1056 - /// Set the `createdAt` field (required) 1057 - pub fn created_at( 1058 - mut self, 1059 - value: impl Into<jacquard_common::types::string::Datetime>, 1060 - ) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> { 1061 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1062 - SubfsRecordBuilder { 1063 - _phantom_state: ::core::marker::PhantomData, 1064 - __unsafe_private_named: self.__unsafe_private_named, 1065 - _phantom: ::core::marker::PhantomData, 1066 - } 1067 - } 1068 - } 1069 - 1070 - impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> { 1071 - /// Set the `fileCount` field (optional) 1072 - pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self { 1073 - self.__unsafe_private_named.1 = value.into(); 1074 - self 1075 - } 1076 - /// Set the `fileCount` field to an Option value (optional) 1077 - pub fn maybe_file_count(mut self, value: Option<i64>) -> Self { 1078 - self.__unsafe_private_named.1 = value; 1079 - self 1080 - } 1081 - } 1082 - 1083 - impl<'a, S> SubfsRecordBuilder<'a, S> 1084 - where 1085 - S: subfs_record_state::State, 1086 - S::Root: subfs_record_state::IsUnset, 1087 - { 1088 - /// Set the `root` field (required) 1089 - pub fn root( 1090 - mut self, 1091 - value: impl Into<crate::place_wisp::subfs::Directory<'a>>, 1092 - ) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> { 1093 - self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into()); 1094 - SubfsRecordBuilder { 1095 - _phantom_state: ::core::marker::PhantomData, 1096 - __unsafe_private_named: self.__unsafe_private_named, 1097 - _phantom: ::core::marker::PhantomData, 1098 - } 1099 - } 1100 - } 1101 - 1102 - impl<'a, S> SubfsRecordBuilder<'a, S> 1103 - where 1104 - S: subfs_record_state::State, 1105 - S::Root: subfs_record_state::IsSet, 1106 - S::CreatedAt: subfs_record_state::IsSet, 1107 - { 1108 - /// Build the final struct 1109 - pub fn build(self) -> SubfsRecord<'a> { 1110 - SubfsRecord { 1111 - created_at: self.__unsafe_private_named.0.unwrap(), 1112 - file_count: self.__unsafe_private_named.1, 1113 - root: self.__unsafe_private_named.2.unwrap(), 1114 - extra_data: Default::default(), 1115 - } 1116 - } 1117 - /// Build the final struct with custom extra_data 1118 - pub fn build_with_data( 1119 - self, 1120 - extra_data: std::collections::BTreeMap< 1121 - jacquard_common::smol_str::SmolStr, 1122 - jacquard_common::types::value::Data<'a>, 1123 - >, 1124 - ) -> SubfsRecord<'a> { 1125 - SubfsRecord { 1126 - created_at: self.__unsafe_private_named.0.unwrap(), 1127 - file_count: self.__unsafe_private_named.1, 1128 - root: self.__unsafe_private_named.2.unwrap(), 1129 - extra_data: Some(extra_data), 1130 - } 1131 - } 1132 - } 1133 - 1134 - impl<'a> SubfsRecord<'a> { 1135 - pub fn uri( 1136 - uri: impl Into<jacquard_common::CowStr<'a>>, 1137 - ) -> Result< 1138 - jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>, 1139 - jacquard_common::types::uri::UriError, 1140 - > { 1141 - jacquard_common::types::uri::RecordUri::try_from_uri( 1142 - jacquard_common::types::string::AtUri::new_cow(uri.into())?, 1143 - ) 1144 - } 1145 - } 1146 - 1147 - /// Typed wrapper for GetRecord response with this collection's record type. 1148 - #[derive( 1149 - serde::Serialize, 1150 - serde::Deserialize, 1151 - Debug, 1152 - Clone, 1153 - PartialEq, 1154 - Eq, 1155 - jacquard_derive::IntoStatic 1156 - )] 1157 - #[serde(rename_all = "camelCase")] 1158 - pub struct SubfsRecordGetRecordOutput<'a> { 1159 - #[serde(skip_serializing_if = "std::option::Option::is_none")] 1160 - #[serde(borrow)] 1161 - pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>, 1162 - #[serde(borrow)] 1163 - pub uri: jacquard_common::types::string::AtUri<'a>, 1164 - #[serde(borrow)] 1165 - pub value: SubfsRecord<'a>, 1166 - } 1167 - 1168 - impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> { 1169 - fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self { 1170 - use jacquard_common::IntoStatic; 1171 - output.value.into_static() 1172 - } 1173 - } 1174 - 1175 - impl jacquard_common::types::collection::Collection for SubfsRecord<'_> { 1176 - const NSID: &'static str = "place.wisp.subfs"; 1177 - type Record = SubfsRecordRecord; 1178 - } 1179 - 1180 - /// Marker type for deserializing records from this collection. 1181 - #[derive(Debug, serde::Serialize, serde::Deserialize)] 1182 - pub struct SubfsRecordRecord; 1183 - impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord { 1184 - const NSID: &'static str = "place.wisp.subfs"; 1185 - const ENCODING: &'static str = "application/json"; 1186 - type Output<'de> = SubfsRecordGetRecordOutput<'de>; 1187 - type Err<'de> = jacquard_common::types::collection::RecordError<'de>; 1188 - } 1189 - 1190 - impl jacquard_common::types::collection::Collection for SubfsRecordRecord { 1191 - const NSID: &'static str = "place.wisp.subfs"; 1192 - type Record = SubfsRecordRecord; 1193 - } 1194 - 1195 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> { 1196 - fn nsid() -> &'static str { 1197 - "place.wisp.subfs" 1198 - } 1199 - fn def_name() -> &'static str { 1200 - "main" 1201 - } 1202 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1203 - lexicon_doc_place_wisp_subfs() 1204 - } 1205 - fn validate( 1206 - &self, 1207 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1208 - if let Some(ref value) = self.file_count { 1209 - if *value > 1000i64 { 1210 - return Err(::jacquard_lexicon::validation::ConstraintError::Maximum { 1211 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 1212 - "file_count", 1213 - ), 1214 - max: 1000i64, 1215 - actual: *value, 1216 - }); 1217 - } 1218 - } 1219 - if let Some(ref value) = self.file_count { 1220 - if *value < 0i64 { 1221 - return Err(::jacquard_lexicon::validation::ConstraintError::Minimum { 1222 - path: ::jacquard_lexicon::validation::ValidationPath::from_field( 1223 - "file_count", 1224 - ), 1225 - min: 0i64, 1226 - actual: *value, 1227 - }); 1228 - } 1229 - } 1230 - Ok(()) 1231 - } 1232 - } 1233 - 1234 - #[jacquard_derive::lexicon] 1235 - #[derive( 1236 - serde::Serialize, 1237 - serde::Deserialize, 1238 - Debug, 1239 - Clone, 1240 - PartialEq, 1241 - Eq, 1242 - jacquard_derive::IntoStatic 1243 - )] 1244 - #[serde(rename_all = "camelCase")] 1245 - pub struct Subfs<'a> { 1246 - /// AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures. 1247 - #[serde(borrow)] 1248 - pub subject: jacquard_common::types::string::AtUri<'a>, 1249 - #[serde(borrow)] 1250 - pub r#type: jacquard_common::CowStr<'a>, 1251 - } 1252 - 1253 - pub mod subfs_state { 1254 - 1255 - pub use crate::builder_types::{Set, Unset, IsSet, IsUnset}; 1256 - #[allow(unused)] 1257 - use ::core::marker::PhantomData; 1258 - mod sealed { 1259 - pub trait Sealed {} 1260 - } 1261 - /// State trait tracking which required fields have been set 1262 - pub trait State: sealed::Sealed { 1263 - type Subject; 1264 - type Type; 1265 - } 1266 - /// Empty state - all required fields are unset 1267 - pub struct Empty(()); 1268 - impl sealed::Sealed for Empty {} 1269 - impl State for Empty { 1270 - type Subject = Unset; 1271 - type Type = Unset; 1272 - } 1273 - ///State transition - sets the `subject` field to Set 1274 - pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>); 1275 - impl<S: State> sealed::Sealed for SetSubject<S> {} 1276 - impl<S: State> State for SetSubject<S> { 1277 - type Subject = Set<members::subject>; 1278 - type Type = S::Type; 1279 - } 1280 - ///State transition - sets the `type` field to Set 1281 - pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>); 1282 - impl<S: State> sealed::Sealed for SetType<S> {} 1283 - impl<S: State> State for SetType<S> { 1284 - type Subject = S::Subject; 1285 - type Type = Set<members::r#type>; 1286 - } 1287 - /// Marker types for field names 1288 - #[allow(non_camel_case_types)] 1289 - pub mod members { 1290 - ///Marker type for the `subject` field 1291 - pub struct subject(()); 1292 - ///Marker type for the `type` field 1293 - pub struct r#type(()); 1294 - } 1295 - } 1296 - 1297 - /// Builder for constructing an instance of this type 1298 - pub struct SubfsBuilder<'a, S: subfs_state::State> { 1299 - _phantom_state: ::core::marker::PhantomData<fn() -> S>, 1300 - __unsafe_private_named: ( 1301 - ::core::option::Option<jacquard_common::types::string::AtUri<'a>>, 1302 - ::core::option::Option<jacquard_common::CowStr<'a>>, 1303 - ), 1304 - _phantom: ::core::marker::PhantomData<&'a ()>, 1305 - } 1306 - 1307 - impl<'a> Subfs<'a> { 1308 - /// Create a new builder for this type 1309 - pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> { 1310 - SubfsBuilder::new() 1311 - } 1312 - } 1313 - 1314 - impl<'a> SubfsBuilder<'a, subfs_state::Empty> { 1315 - /// Create a new builder with all fields unset 1316 - pub fn new() -> Self { 1317 - SubfsBuilder { 1318 - _phantom_state: ::core::marker::PhantomData, 1319 - __unsafe_private_named: (None, None), 1320 - _phantom: ::core::marker::PhantomData, 1321 - } 1322 - } 1323 - } 1324 - 1325 - impl<'a, S> SubfsBuilder<'a, S> 1326 - where 1327 - S: subfs_state::State, 1328 - S::Subject: subfs_state::IsUnset, 1329 - { 1330 - /// Set the `subject` field (required) 1331 - pub fn subject( 1332 - mut self, 1333 - value: impl Into<jacquard_common::types::string::AtUri<'a>>, 1334 - ) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> { 1335 - self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into()); 1336 - SubfsBuilder { 1337 - _phantom_state: ::core::marker::PhantomData, 1338 - __unsafe_private_named: self.__unsafe_private_named, 1339 - _phantom: ::core::marker::PhantomData, 1340 - } 1341 - } 1342 - } 1343 - 1344 - impl<'a, S> SubfsBuilder<'a, S> 1345 - where 1346 - S: subfs_state::State, 1347 - S::Type: subfs_state::IsUnset, 1348 - { 1349 - /// Set the `type` field (required) 1350 - pub fn r#type( 1351 - mut self, 1352 - value: impl Into<jacquard_common::CowStr<'a>>, 1353 - ) -> SubfsBuilder<'a, subfs_state::SetType<S>> { 1354 - self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into()); 1355 - SubfsBuilder { 1356 - _phantom_state: ::core::marker::PhantomData, 1357 - __unsafe_private_named: self.__unsafe_private_named, 1358 - _phantom: ::core::marker::PhantomData, 1359 - } 1360 - } 1361 - } 1362 - 1363 - impl<'a, S> SubfsBuilder<'a, S> 1364 - where 1365 - S: subfs_state::State, 1366 - S::Subject: subfs_state::IsSet, 1367 - S::Type: subfs_state::IsSet, 1368 - { 1369 - /// Build the final struct 1370 - pub fn build(self) -> Subfs<'a> { 1371 - Subfs { 1372 - subject: self.__unsafe_private_named.0.unwrap(), 1373 - r#type: self.__unsafe_private_named.1.unwrap(), 1374 - extra_data: Default::default(), 1375 - } 1376 - } 1377 - /// Build the final struct with custom extra_data 1378 - pub fn build_with_data( 1379 - self, 1380 - extra_data: std::collections::BTreeMap< 1381 - jacquard_common::smol_str::SmolStr, 1382 - jacquard_common::types::value::Data<'a>, 1383 - >, 1384 - ) -> Subfs<'a> { 1385 - Subfs { 1386 - subject: self.__unsafe_private_named.0.unwrap(), 1387 - r#type: self.__unsafe_private_named.1.unwrap(), 1388 - extra_data: Some(extra_data), 1389 - } 1390 - } 1391 - } 1392 - 1393 - impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> { 1394 - fn nsid() -> &'static str { 1395 - "place.wisp.subfs" 1396 - } 1397 - fn def_name() -> &'static str { 1398 - "subfs" 1399 - } 1400 - fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> { 1401 - lexicon_doc_place_wisp_subfs() 1402 - } 1403 - fn validate( 1404 - &self, 1405 - ) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> { 1406 - Ok(()) 1407 - } 1408 - }
-16
rust-cli/default.nix
··· 1 - { 2 - rustPlatform, 3 - glibc, 4 - }: 5 - rustPlatform.buildRustPackage { 6 - name = "wisp-cli"; 7 - src = ./.; 8 - cargoLock = { 9 - lockFile = ./Cargo.lock; 10 - outputHashes = { 11 - "jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA="; 12 - }; 13 - }; 14 - buildInputs = [glibc.static]; 15 - RUSTFLAGS = ["-C" "target-feature=+crt-static"]; 16 - }
-96
rust-cli/flake.lock
··· 1 - { 2 - "nodes": { 3 - "flake-utils": { 4 - "inputs": { 5 - "systems": "systems" 6 - }, 7 - "locked": { 8 - "lastModified": 1731533236, 9 - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 - "owner": "numtide", 11 - "repo": "flake-utils", 12 - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 - "type": "github" 14 - }, 15 - "original": { 16 - "owner": "numtide", 17 - "repo": "flake-utils", 18 - "type": "github" 19 - } 20 - }, 21 - "nixpkgs": { 22 - "locked": { 23 - "lastModified": 1767640445, 24 - "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", 25 - "owner": "NixOS", 26 - "repo": "nixpkgs", 27 - "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", 28 - "type": "github" 29 - }, 30 - "original": { 31 - "owner": "NixOS", 32 - "ref": "nixos-unstable", 33 - "repo": "nixpkgs", 34 - "type": "github" 35 - } 36 - }, 37 - "nixpkgs_2": { 38 - "locked": { 39 - "lastModified": 1744536153, 40 - "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", 41 - "owner": "NixOS", 42 - "repo": "nixpkgs", 43 - "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", 44 - "type": "github" 45 - }, 46 - "original": { 47 - "owner": "NixOS", 48 - "ref": "nixpkgs-unstable", 49 - "repo": "nixpkgs", 50 - "type": "github" 51 - } 52 - }, 53 - "root": { 54 - "inputs": { 55 - "flake-utils": "flake-utils", 56 - "nixpkgs": "nixpkgs", 57 - "rust-overlay": "rust-overlay" 58 - } 59 - }, 60 - "rust-overlay": { 61 - "inputs": { 62 - "nixpkgs": "nixpkgs_2" 63 - }, 64 - "locked": { 65 - "lastModified": 1767667566, 66 - "narHash": "sha256-COy+yxZGuhQRVD1r4bWVgeFt1GB+IB1k5WRpDKbLfI8=", 67 - "owner": "oxalica", 68 - "repo": "rust-overlay", 69 - "rev": "056ce5b125ab32ffe78c7d3e394d9da44733c95e", 70 - "type": "github" 71 - }, 72 - "original": { 73 - "owner": "oxalica", 74 - "repo": "rust-overlay", 75 - "type": "github" 76 - } 77 - }, 78 - "systems": { 79 - "locked": { 80 - "lastModified": 1681028828, 81 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 82 - "owner": "nix-systems", 83 - "repo": "default", 84 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 85 - "type": "github" 86 - }, 87 - "original": { 88 - "owner": "nix-systems", 89 - "repo": "default", 90 - "type": "github" 91 - } 92 - } 93 - }, 94 - "root": "root", 95 - "version": 7 96 - }
-136
rust-cli/flake.nix
··· 1 - { 2 - inputs = { 3 - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 - rust-overlay.url = "github:oxalica/rust-overlay"; 5 - flake-utils.url = "github:numtide/flake-utils"; 6 - }; 7 - 8 - outputs = { self, nixpkgs, rust-overlay, flake-utils }: 9 - flake-utils.lib.eachDefaultSystem (system: 10 - let 11 - overlays = [ (import rust-overlay) ]; 12 - pkgs = import nixpkgs { inherit system overlays; }; 13 - 14 - rustToolchain = pkgs.rust-bin.stable.latest.default.override { 15 - targets = [ 16 - "x86_64-unknown-linux-musl" 17 - "aarch64-unknown-linux-musl" 18 - "x86_64-apple-darwin" 19 - "aarch64-apple-darwin" 20 - ]; 21 - }; 22 - 23 - cargoLockConfig = { 24 - lockFile = ./Cargo.lock; 25 - outputHashes = { 26 - "jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA="; 27 - }; 28 - }; 29 - 30 - # Native build for current system 31 - native = pkgs.rustPlatform.buildRustPackage { 32 - pname = "wisp-cli"; 33 - version = "0.4.2"; 34 - src = ./.; 35 - cargoLock = cargoLockConfig; 36 - nativeBuildInputs = [ rustToolchain ]; 37 - }; 38 - 39 - # Cross-compilation targets (Linux from macOS needs zigbuild) 40 - linuxTargets = let 41 - zigbuildPkgs = pkgs; 42 - in { 43 - x86_64-linux = pkgs.stdenv.mkDerivation { 44 - pname = "wisp-cli-x86_64-linux"; 45 - version = "0.4.2"; 46 - src = ./.; 47 - 48 - nativeBuildInputs = [ 49 - rustToolchain 50 - pkgs.cargo-zigbuild 51 - pkgs.zig 52 - ]; 53 - 54 - buildPhase = '' 55 - export HOME=$(mktemp -d) 56 - cargo zigbuild --release --target x86_64-unknown-linux-musl 57 - ''; 58 - 59 - installPhase = '' 60 - mkdir -p $out/bin 61 - cp target/x86_64-unknown-linux-musl/release/wisp-cli $out/bin/ 62 - ''; 63 - 64 - # Skip Nix's cargo vendor - we use network 65 - dontConfigure = true; 66 - dontFixup = true; 67 - }; 68 - 69 - aarch64-linux = pkgs.stdenv.mkDerivation { 70 - pname = "wisp-cli-aarch64-linux"; 71 - version = "0.4.2"; 72 - src = ./.; 73 - 74 - nativeBuildInputs = [ 75 - rustToolchain 76 - pkgs.cargo-zigbuild 77 - pkgs.zig 78 - ]; 79 - 80 - buildPhase = '' 81 - export HOME=$(mktemp -d) 82 - cargo zigbuild --release --target aarch64-unknown-linux-musl 83 - ''; 84 - 85 - installPhase = '' 86 - mkdir -p $out/bin 87 - cp target/aarch64-unknown-linux-musl/release/wisp-cli $out/bin/ 88 - ''; 89 - 90 - dontConfigure = true; 91 - dontFixup = true; 92 - }; 93 - }; 94 - 95 - in { 96 - packages = { 97 - default = native; 98 - inherit native; 99 - 100 - # macOS universal binary 101 - macos-universal = pkgs.stdenv.mkDerivation { 102 - pname = "wisp-cli-macos-universal"; 103 - version = "0.4.2"; 104 - src = ./.; 105 - 106 - nativeBuildInputs = [ rustToolchain pkgs.darwin.lipo ]; 107 - 108 - buildPhase = '' 109 - export HOME=$(mktemp -d) 110 - cargo build --release --target aarch64-apple-darwin 111 - cargo build --release --target x86_64-apple-darwin 112 - ''; 113 - 114 - installPhase = '' 115 - mkdir -p $out/bin 116 - lipo -create \ 117 - target/aarch64-apple-darwin/release/wisp-cli \ 118 - target/x86_64-apple-darwin/release/wisp-cli \ 119 - -output $out/bin/wisp-cli 120 - ''; 121 - 122 - dontConfigure = true; 123 - dontFixup = true; 124 - }; 125 - } // (if pkgs.stdenv.isDarwin then linuxTargets else {}); 126 - 127 - devShells.default = pkgs.mkShell { 128 - buildInputs = [ 129 - rustToolchain 130 - pkgs.cargo-zigbuild 131 - pkgs.zig 132 - ]; 133 - }; 134 - } 135 - ); 136 - }
-89
rust-cli/src/blob_map.rs
··· 1 - use jacquard_common::types::blob::BlobRef; 2 - use jacquard_common::IntoStatic; 3 - use std::collections::HashMap; 4 - 5 - use wisp_lexicons::place_wisp::fs::{Directory, EntryNode}; 6 - 7 - /// Extract blob information from a directory tree 8 - /// Returns a map of file paths to their blob refs and CIDs 9 - /// 10 - /// This mirrors the TypeScript implementation in src/lib/wisp-utils.ts lines 275-302 11 - pub fn extract_blob_map( 12 - directory: &Directory, 13 - ) -> HashMap<String, (BlobRef<'static>, String)> { 14 - extract_blob_map_recursive(directory, String::new()) 15 - } 16 - 17 - fn extract_blob_map_recursive( 18 - directory: &Directory, 19 - current_path: String, 20 - ) -> HashMap<String, (BlobRef<'static>, String)> { 21 - let mut blob_map = HashMap::new(); 22 - 23 - for entry in &directory.entries { 24 - let full_path = if current_path.is_empty() { 25 - entry.name.to_string() 26 - } else { 27 - format!("{}/{}", current_path, entry.name) 28 - }; 29 - 30 - match &entry.node { 31 - EntryNode::File(file_node) => { 32 - // Extract CID from blob ref 33 - // BlobRef is an enum with Blob variant, which has a ref field (CidLink) 34 - let blob_ref = &file_node.blob; 35 - let cid_string = blob_ref.blob().r#ref.to_string(); 36 - 37 - // Store with full path (mirrors TypeScript implementation) 38 - blob_map.insert( 39 - full_path, 40 - (blob_ref.clone().into_static(), cid_string) 41 - ); 42 - } 43 - EntryNode::Directory(subdir) => { 44 - let sub_map = extract_blob_map_recursive(subdir, full_path); 45 - blob_map.extend(sub_map); 46 - } 47 - EntryNode::Subfs(_) => { 48 - // Subfs nodes don't contain blobs directly - they reference other records 49 - // Skip them in blob map extraction 50 - } 51 - EntryNode::Unknown(_) => { 52 - // Skip unknown node types 53 - } 54 - } 55 - } 56 - 57 - blob_map 58 - } 59 - 60 - /// Normalize file path by removing base folder prefix 61 - /// Example: "cobblemon/index.html" -> "index.html" 62 - /// 63 - /// Note: This function is kept for reference but is no longer used in production code. 64 - /// The TypeScript server has a similar normalization (src/routes/wisp.ts line 291) to handle 65 - /// uploads that include a base folder prefix, but our CLI doesn't need this since we 66 - /// track full paths consistently. 67 - #[allow(dead_code)] 68 - pub fn normalize_path(path: &str) -> String { 69 - // Remove base folder prefix (everything before first /) 70 - if let Some(idx) = path.find('/') { 71 - path[idx + 1..].to_string() 72 - } else { 73 - path.to_string() 74 - } 75 - } 76 - 77 - #[cfg(test)] 78 - mod tests { 79 - use super::*; 80 - 81 - #[test] 82 - fn test_normalize_path() { 83 - assert_eq!(normalize_path("index.html"), "index.html"); 84 - assert_eq!(normalize_path("cobblemon/index.html"), "index.html"); 85 - assert_eq!(normalize_path("folder/subfolder/file.txt"), "subfolder/file.txt"); 86 - assert_eq!(normalize_path("a/b/c/d.txt"), "b/c/d.txt"); 87 - } 88 - } 89 -
-66
rust-cli/src/cid.rs
··· 1 - use jacquard_common::types::cid::IpldCid; 2 - use sha2::{Digest, Sha256}; 3 - 4 - /// Compute CID (Content Identifier) for blob content 5 - /// Uses the same algorithm as AT Protocol: CIDv1 with raw codec (0x55) and SHA-256 6 - /// 7 - /// CRITICAL: This must be called on BASE64-ENCODED GZIPPED content, not just gzipped content 8 - /// 9 - /// Based on @atproto/common/src/ipld.ts sha256RawToCid implementation 10 - pub fn compute_cid(content: &[u8]) -> String { 11 - // Use node crypto to compute sha256 hash (same as AT Protocol) 12 - let hash = Sha256::digest(content); 13 - 14 - // Create multihash (code 0x12 = sha2-256) 15 - let multihash = multihash::Multihash::wrap(0x12, &hash) 16 - .expect("SHA-256 hash should always fit in multihash"); 17 - 18 - // Create CIDv1 with raw codec (0x55) 19 - let cid = IpldCid::new_v1(0x55, multihash); 20 - 21 - // Convert to base32 string representation 22 - cid.to_string_of_base(multibase::Base::Base32Lower) 23 - .unwrap_or_else(|_| cid.to_string()) 24 - } 25 - 26 - #[cfg(test)] 27 - mod tests { 28 - use super::*; 29 - use base64::Engine; 30 - 31 - #[test] 32 - fn test_compute_cid() { 33 - // Test with a simple string: "hello" 34 - let content = b"hello"; 35 - let cid = compute_cid(content); 36 - 37 - // CID should start with 'baf' for raw codec base32 38 - assert!(cid.starts_with("baf")); 39 - } 40 - 41 - #[test] 42 - fn test_compute_cid_base64_encoded() { 43 - // Simulate the actual use case: gzipped then base64 encoded 44 - use flate2::write::GzEncoder; 45 - use flate2::Compression; 46 - use std::io::Write; 47 - 48 - let original = b"hello world"; 49 - 50 - // Gzip compress 51 - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); 52 - encoder.write_all(original).unwrap(); 53 - let gzipped = encoder.finish().unwrap(); 54 - 55 - // Base64 encode the gzipped data 56 - let base64_bytes = base64::prelude::BASE64_STANDARD.encode(&gzipped).into_bytes(); 57 - 58 - // Compute CID on the base64 bytes 59 - let cid = compute_cid(&base64_bytes); 60 - 61 - // Should be a valid CID 62 - assert!(cid.starts_with("baf")); 63 - assert!(cid.len() > 10); 64 - } 65 - } 66 -
-71
rust-cli/src/download.rs
··· 1 - use base64::Engine; 2 - use bytes::Bytes; 3 - use flate2::read::GzDecoder; 4 - use jacquard_common::types::blob::BlobRef; 5 - use miette::IntoDiagnostic; 6 - use std::io::Read; 7 - use url::Url; 8 - 9 - /// Download a blob from the PDS 10 - pub async fn download_blob(pds_url: &Url, blob_ref: &BlobRef<'_>, did: &str) -> miette::Result<Bytes> { 11 - // Extract CID from blob ref 12 - let cid = blob_ref.blob().r#ref.to_string(); 13 - 14 - // Construct blob download URL 15 - // The correct endpoint is: /xrpc/com.atproto.sync.getBlob?did={did}&cid={cid} 16 - let blob_url = pds_url 17 - .join(&format!("/xrpc/com.atproto.sync.getBlob?did={}&cid={}", did, cid)) 18 - .into_diagnostic()?; 19 - 20 - let client = reqwest::Client::new(); 21 - let response = client 22 - .get(blob_url) 23 - .send() 24 - .await 25 - .into_diagnostic()?; 26 - 27 - if !response.status().is_success() { 28 - return Err(miette::miette!( 29 - "Failed to download blob: {}", 30 - response.status() 31 - )); 32 - } 33 - 34 - let bytes = response.bytes().await.into_diagnostic()?; 35 - Ok(bytes) 36 - } 37 - 38 - /// Decompress and decode a blob (base64 + gzip) 39 - pub fn decompress_blob(data: &[u8], is_base64: bool, is_gzipped: bool) -> miette::Result<Vec<u8>> { 40 - let mut current_data = data.to_vec(); 41 - 42 - // First, decode base64 if needed 43 - if is_base64 { 44 - current_data = base64::prelude::BASE64_STANDARD 45 - .decode(&current_data) 46 - .into_diagnostic()?; 47 - } 48 - 49 - // Then, decompress gzip if needed 50 - if is_gzipped { 51 - let mut decoder = GzDecoder::new(&current_data[..]); 52 - let mut decompressed = Vec::new(); 53 - decoder.read_to_end(&mut decompressed).into_diagnostic()?; 54 - current_data = decompressed; 55 - } 56 - 57 - Ok(current_data) 58 - } 59 - 60 - /// Download and decompress a blob 61 - pub async fn download_and_decompress_blob( 62 - pds_url: &Url, 63 - blob_ref: &BlobRef<'_>, 64 - did: &str, 65 - is_base64: bool, 66 - is_gzipped: bool, 67 - ) -> miette::Result<Vec<u8>> { 68 - let data = download_blob(pds_url, blob_ref, did).await?; 69 - decompress_blob(&data, is_base64, is_gzipped) 70 - } 71 -
-149
rust-cli/src/ignore_patterns.rs
··· 1 - use globset::{Glob, GlobSet, GlobSetBuilder}; 2 - use serde::{Deserialize, Serialize}; 3 - use std::path::Path; 4 - use miette::IntoDiagnostic; 5 - 6 - #[derive(Debug, Deserialize, Serialize)] 7 - struct IgnoreConfig { 8 - patterns: Vec<String>, 9 - } 10 - 11 - /// Load ignore patterns from the default .wispignore.json file 12 - fn load_default_patterns() -> miette::Result<Vec<String>> { 13 - // Path to the default ignore patterns JSON file (in the monorepo root) 14 - let default_json_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../.wispignore.json"); 15 - 16 - match std::fs::read_to_string(default_json_path) { 17 - Ok(contents) => { 18 - let config: IgnoreConfig = serde_json::from_str(&contents).into_diagnostic()?; 19 - Ok(config.patterns) 20 - } 21 - Err(_) => { 22 - // If the default file doesn't exist, return hardcoded patterns as fallback 23 - eprintln!("⚠️ Default .wispignore.json not found, using hardcoded patterns"); 24 - Ok(get_hardcoded_patterns()) 25 - } 26 - } 27 - } 28 - 29 - /// Hardcoded fallback patterns (same as in .wispignore.json) 30 - fn get_hardcoded_patterns() -> Vec<String> { 31 - vec![ 32 - ".git".to_string(), 33 - ".git/**".to_string(), 34 - ".github".to_string(), 35 - ".github/**".to_string(), 36 - ".gitlab".to_string(), 37 - ".gitlab/**".to_string(), 38 - ".DS_Store".to_string(), 39 - ".wisp.metadata.json".to_string(), 40 - ".env".to_string(), 41 - ".env.*".to_string(), 42 - "node_modules".to_string(), 43 - "node_modules/**".to_string(), 44 - "Thumbs.db".to_string(), 45 - "desktop.ini".to_string(), 46 - "._*".to_string(), 47 - ".Spotlight-V100".to_string(), 48 - ".Spotlight-V100/**".to_string(), 49 - ".Trashes".to_string(), 50 - ".Trashes/**".to_string(), 51 - ".fseventsd".to_string(), 52 - ".fseventsd/**".to_string(), 53 - ".cache".to_string(), 54 - ".cache/**".to_string(), 55 - ".temp".to_string(), 56 - ".temp/**".to_string(), 57 - ".tmp".to_string(), 58 - ".tmp/**".to_string(), 59 - "__pycache__".to_string(), 60 - "__pycache__/**".to_string(), 61 - "*.pyc".to_string(), 62 - ".venv".to_string(), 63 - ".venv/**".to_string(), 64 - "venv".to_string(), 65 - "venv/**".to_string(), 66 - "env".to_string(), 67 - "env/**".to_string(), 68 - "*.swp".to_string(), 69 - "*.swo".to_string(), 70 - "*~".to_string(), 71 - ".tangled".to_string(), 72 - ".tangled/**".to_string(), 73 - ] 74 - } 75 - 76 - /// Load custom ignore patterns from a .wispignore file in the given directory 77 - fn load_wispignore_file(dir_path: &Path) -> miette::Result<Vec<String>> { 78 - let wispignore_path = dir_path.join(".wispignore"); 79 - 80 - if !wispignore_path.exists() { 81 - return Ok(Vec::new()); 82 - } 83 - 84 - let contents = std::fs::read_to_string(&wispignore_path).into_diagnostic()?; 85 - 86 - // Parse gitignore-style file (one pattern per line, # for comments) 87 - let patterns: Vec<String> = contents 88 - .lines() 89 - .filter_map(|line| { 90 - let line = line.trim(); 91 - // Skip empty lines and comments 92 - if line.is_empty() || line.starts_with('#') { 93 - None 94 - } else { 95 - Some(line.to_string()) 96 - } 97 - }) 98 - .collect(); 99 - 100 - if !patterns.is_empty() { 101 - println!("Loaded {} custom patterns from .wispignore", patterns.len()); 102 - } 103 - 104 - Ok(patterns) 105 - } 106 - 107 - /// Build a GlobSet from a list of patterns 108 - fn build_globset(patterns: Vec<String>) -> miette::Result<GlobSet> { 109 - let mut builder = GlobSetBuilder::new(); 110 - 111 - for pattern in patterns { 112 - let glob = Glob::new(&pattern).into_diagnostic()?; 113 - builder.add(glob); 114 - } 115 - 116 - let globset = builder.build().into_diagnostic()?; 117 - Ok(globset) 118 - } 119 - 120 - /// IgnoreMatcher handles checking if paths should be ignored 121 - pub struct IgnoreMatcher { 122 - globset: GlobSet, 123 - } 124 - 125 - impl IgnoreMatcher { 126 - /// Create a new IgnoreMatcher for the given directory 127 - /// Loads default patterns and any custom .wispignore file 128 - pub fn new(dir_path: &Path) -> miette::Result<Self> { 129 - let mut all_patterns = load_default_patterns()?; 130 - 131 - // Load custom patterns from .wispignore 132 - let custom_patterns = load_wispignore_file(dir_path)?; 133 - all_patterns.extend(custom_patterns); 134 - 135 - let globset = build_globset(all_patterns)?; 136 - 137 - Ok(Self { globset }) 138 - } 139 - 140 - /// Check if the given path (relative to site root) should be ignored 141 - pub fn is_ignored(&self, path: &str) -> bool { 142 - self.globset.is_match(path) 143 - } 144 - 145 - /// Check if a filename should be ignored (checks just the filename, not full path) 146 - pub fn is_filename_ignored(&self, filename: &str) -> bool { 147 - self.globset.is_match(filename) 148 - } 149 - }
-993
rust-cli/src/main.rs
··· 1 - mod cid; 2 - mod blob_map; 3 - mod metadata; 4 - mod download; 5 - mod pull; 6 - mod serve; 7 - mod subfs_utils; 8 - mod redirects; 9 - mod ignore_patterns; 10 - 11 - use clap::{Parser, Subcommand}; 12 - use jacquard::CowStr; 13 - use jacquard::client::{Agent, FileAuthStore, AgentSessionExt, MemoryCredentialSession, AgentSession}; 14 - use jacquard::oauth::client::OAuthClient; 15 - use jacquard::oauth::loopback::LoopbackConfig; 16 - use jacquard::prelude::IdentityResolver; 17 - use jacquard_common::types::string::{Datetime, Rkey, RecordKey, AtUri}; 18 - use jacquard_common::types::blob::MimeType; 19 - use miette::IntoDiagnostic; 20 - use std::path::{Path, PathBuf}; 21 - use std::collections::HashMap; 22 - use flate2::Compression; 23 - use flate2::write::GzEncoder; 24 - use std::io::Write; 25 - use base64::Engine; 26 - use futures::stream::{self, StreamExt}; 27 - use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; 28 - 29 - use wisp_lexicons::place_wisp::fs::*; 30 - use wisp_lexicons::place_wisp::settings::*; 31 - 32 - /// Maximum number of concurrent file uploads to the PDS 33 - const MAX_CONCURRENT_UPLOADS: usize = 2; 34 - 35 - /// Limits for caching on wisp.place (from @wisp/constants) 36 - const MAX_FILE_COUNT: usize = 1000; 37 - const MAX_SITE_SIZE: usize = 300 * 1024 * 1024; // 300MB 38 - 39 - #[derive(Parser, Debug)] 40 - #[command(author, version, about = "wisp.place CLI tool")] 41 - struct Args { 42 - #[command(subcommand)] 43 - command: Option<Commands>, 44 - 45 - // Deploy arguments (when no subcommand is specified) 46 - /// Handle (e.g., alice.bsky.social), DID, or PDS URL 47 - input: Option<CowStr<'static>>, 48 - 49 - /// Path to the directory containing your static site 50 - #[arg(short, long)] 51 - path: Option<PathBuf>, 52 - 53 - /// Site name (defaults to directory name) 54 - #[arg(short, long)] 55 - site: Option<String>, 56 - 57 - /// Path to auth store file 58 - #[arg(long)] 59 - store: Option<String>, 60 - 61 - /// App Password for authentication 62 - #[arg(long)] 63 - password: Option<CowStr<'static>>, 64 - 65 - /// Enable directory listing mode for paths without index files 66 - #[arg(long)] 67 - directory: bool, 68 - 69 - /// Enable SPA mode (serve index.html for all routes) 70 - #[arg(long)] 71 - spa: bool, 72 - 73 - /// Skip confirmation prompts (automatically accept warnings) 74 - #[arg(short = 'y', long)] 75 - yes: bool, 76 - } 77 - 78 - #[derive(Subcommand, Debug)] 79 - enum Commands { 80 - /// Deploy a static site to wisp.place (default command) 81 - Deploy { 82 - /// Handle (e.g., alice.bsky.social), DID, or PDS URL 83 - input: CowStr<'static>, 84 - 85 - /// Path to the directory containing your static site 86 - #[arg(short, long, default_value = ".")] 87 - path: PathBuf, 88 - 89 - /// Site name (defaults to directory name) 90 - #[arg(short, long)] 91 - site: Option<String>, 92 - 93 - /// Path to auth store file (will be created if missing, only used with OAuth) 94 - #[arg(long, default_value = "/tmp/wisp-oauth-session.json")] 95 - store: String, 96 - 97 - /// App Password for authentication (alternative to OAuth) 98 - #[arg(long)] 99 - password: Option<CowStr<'static>>, 100 - 101 - /// Enable directory listing mode for paths without index files 102 - #[arg(long)] 103 - directory: bool, 104 - 105 - /// Enable SPA mode (serve index.html for all routes) 106 - #[arg(long)] 107 - spa: bool, 108 - 109 - /// Skip confirmation prompts (automatically accept warnings) 110 - #[arg(short = 'y', long)] 111 - yes: bool, 112 - }, 113 - /// Pull a site from the PDS to a local directory 114 - Pull { 115 - /// Handle (e.g., alice.bsky.social) or DID 116 - input: CowStr<'static>, 117 - 118 - /// Site name (record key) 119 - #[arg(short, long)] 120 - site: String, 121 - 122 - /// Output directory for the downloaded site 123 - #[arg(short, long, default_value = ".")] 124 - path: PathBuf, 125 - }, 126 - /// Serve a site locally with real-time firehose updates 127 - Serve { 128 - /// Handle (e.g., alice.bsky.social) or DID 129 - input: CowStr<'static>, 130 - 131 - /// Site name (record key) 132 - #[arg(short, long)] 133 - site: String, 134 - 135 - /// Output directory for the site files 136 - #[arg(short, long, default_value = ".")] 137 - path: PathBuf, 138 - 139 - /// Port to serve on 140 - #[arg(short = 'P', long, default_value = "8080")] 141 - port: u16, 142 - }, 143 - } 144 - 145 - #[tokio::main] 146 - async fn main() -> miette::Result<()> { 147 - let args = Args::parse(); 148 - 149 - let result = match args.command { 150 - Some(Commands::Deploy { input, path, site, store, password, directory, spa, yes }) => { 151 - // Dispatch to appropriate authentication method 152 - if let Some(password) = password { 153 - run_with_app_password(input, password, path, site, directory, spa, yes).await 154 - } else { 155 - run_with_oauth(input, store, path, site, directory, spa, yes).await 156 - } 157 - } 158 - Some(Commands::Pull { input, site, path }) => { 159 - pull::pull_site(input, CowStr::from(site), path).await 160 - } 161 - Some(Commands::Serve { input, site, path, port }) => { 162 - serve::serve_site(input, CowStr::from(site), path, port).await 163 - } 164 - None => { 165 - // Legacy mode: if input is provided, assume deploy command 166 - if let Some(input) = args.input { 167 - let path = args.path.unwrap_or_else(|| PathBuf::from(".")); 168 - let store = args.store.unwrap_or_else(|| "/tmp/wisp-oauth-session.json".to_string()); 169 - 170 - // Dispatch to appropriate authentication method 171 - if let Some(password) = args.password { 172 - run_with_app_password(input, password, path, args.site, args.directory, args.spa, args.yes).await 173 - } else { 174 - run_with_oauth(input, store, path, args.site, args.directory, args.spa, args.yes).await 175 - } 176 - } else { 177 - // No command and no input, show help 178 - use clap::CommandFactory; 179 - Args::command().print_help().into_diagnostic()?; 180 - Ok(()) 181 - } 182 - } 183 - }; 184 - 185 - // Force exit to avoid hanging on background tasks/connections 186 - match result { 187 - Ok(_) => std::process::exit(0), 188 - Err(e) => { 189 - eprintln!("{:?}", e); 190 - std::process::exit(1) 191 - } 192 - } 193 - } 194 - 195 - /// Run deployment with app password authentication 196 - async fn run_with_app_password( 197 - input: CowStr<'static>, 198 - password: CowStr<'static>, 199 - path: PathBuf, 200 - site: Option<String>, 201 - directory: bool, 202 - spa: bool, 203 - yes: bool, 204 - ) -> miette::Result<()> { 205 - let (session, auth) = 206 - MemoryCredentialSession::authenticated(input, password, None, None).await?; 207 - println!("Signed in as {}", auth.handle); 208 - 209 - let agent: Agent<_> = Agent::from(session); 210 - deploy_site(&agent, path, site, directory, spa, yes).await 211 - } 212 - 213 - /// Run deployment with OAuth authentication 214 - async fn run_with_oauth( 215 - input: CowStr<'static>, 216 - store: String, 217 - path: PathBuf, 218 - site: Option<String>, 219 - directory: bool, 220 - spa: bool, 221 - yes: bool, 222 - ) -> miette::Result<()> { 223 - use jacquard::oauth::scopes::Scope; 224 - use jacquard::oauth::atproto::AtprotoClientMetadata; 225 - use jacquard::oauth::session::ClientData; 226 - use url::Url; 227 - 228 - // Request the necessary scopes for wisp.place (including settings) 229 - let scopes = Scope::parse_multiple("atproto repo:place.wisp.fs repo:place.wisp.subfs repo:place.wisp.settings blob:*/*") 230 - .map_err(|e| miette::miette!("Failed to parse scopes: {:?}", e))?; 231 - 232 - // Create redirect URIs that match the loopback server (port 4000, path /oauth/callback) 233 - let redirect_uris = vec![ 234 - Url::parse("http://127.0.0.1:4000/oauth/callback").into_diagnostic()?, 235 - Url::parse("http://[::1]:4000/oauth/callback").into_diagnostic()?, 236 - ]; 237 - 238 - // Create client metadata with matching redirect URIs and scopes 239 - let client_data = ClientData { 240 - keyset: None, 241 - config: AtprotoClientMetadata::new_localhost( 242 - Some(redirect_uris), 243 - Some(scopes), 244 - ), 245 - }; 246 - 247 - let oauth = OAuthClient::new(FileAuthStore::new(&store), client_data); 248 - 249 - let session = oauth 250 - .login_with_local_server(input, Default::default(), LoopbackConfig::default()) 251 - .await?; 252 - 253 - let agent: Agent<_> = Agent::from(session); 254 - deploy_site(&agent, path, site, directory, spa, yes).await 255 - } 256 - 257 - /// Scan directory to count files and calculate total size 258 - /// Returns (file_count, total_size_bytes) 259 - fn scan_directory_stats( 260 - dir_path: &Path, 261 - ignore_matcher: &ignore_patterns::IgnoreMatcher, 262 - current_path: String, 263 - ) -> miette::Result<(usize, u64)> { 264 - let mut file_count = 0; 265 - let mut total_size = 0u64; 266 - 267 - let dir_entries: Vec<_> = std::fs::read_dir(dir_path) 268 - .into_diagnostic()? 269 - .collect::<Result<Vec<_>, _>>() 270 - .into_diagnostic()?; 271 - 272 - for entry in dir_entries { 273 - let path = entry.path(); 274 - let name = entry.file_name(); 275 - let name_str = name.to_str() 276 - .ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))? 277 - .to_string(); 278 - 279 - let full_path = if current_path.is_empty() { 280 - name_str.clone() 281 - } else { 282 - format!("{}/{}", current_path, name_str) 283 - }; 284 - 285 - // Skip files/directories that match ignore patterns 286 - if ignore_matcher.is_ignored(&full_path) || ignore_matcher.is_filename_ignored(&name_str) { 287 - continue; 288 - } 289 - 290 - let metadata = entry.metadata().into_diagnostic()?; 291 - 292 - if metadata.is_file() { 293 - file_count += 1; 294 - total_size += metadata.len(); 295 - } else if metadata.is_dir() { 296 - let subdir_path = if current_path.is_empty() { 297 - name_str 298 - } else { 299 - format!("{}/{}", current_path, name_str) 300 - }; 301 - let (sub_count, sub_size) = scan_directory_stats(&path, ignore_matcher, subdir_path)?; 302 - file_count += sub_count; 303 - total_size += sub_size; 304 - } 305 - } 306 - 307 - Ok((file_count, total_size)) 308 - } 309 - 310 - /// Deploy the site using the provided agent 311 - async fn deploy_site( 312 - agent: &Agent<impl jacquard::client::AgentSession + IdentityResolver>, 313 - path: PathBuf, 314 - site: Option<String>, 315 - directory_listing: bool, 316 - spa_mode: bool, 317 - skip_prompts: bool, 318 - ) -> miette::Result<()> { 319 - // Verify the path exists 320 - if !path.exists() { 321 - return Err(miette::miette!("Path does not exist: {}", path.display())); 322 - } 323 - 324 - // Get site name 325 - let site_name = site.unwrap_or_else(|| { 326 - path 327 - .file_name() 328 - .and_then(|n| n.to_str()) 329 - .unwrap_or("site") 330 - .to_string() 331 - }); 332 - 333 - println!("Deploying site '{}'...", site_name); 334 - 335 - // Scan directory to check file count and size 336 - let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?; 337 - let (file_count, total_size) = scan_directory_stats(&path, &ignore_matcher, String::new())?; 338 - 339 - let size_mb = total_size as f64 / (1024.0 * 1024.0); 340 - println!("Scanned: {} files, {:.1} MB total", file_count, size_mb); 341 - 342 - // Check if limits are exceeded 343 - let exceeds_file_count = file_count > MAX_FILE_COUNT; 344 - let exceeds_size = total_size > MAX_SITE_SIZE as u64; 345 - 346 - if exceeds_file_count || exceeds_size { 347 - println!("\n⚠️ Warning: Your site exceeds wisp.place caching limits:"); 348 - 349 - if exceeds_file_count { 350 - println!(" • File count: {} (limit: {})", file_count, MAX_FILE_COUNT); 351 - } 352 - 353 - if exceeds_size { 354 - let size_mb = total_size as f64 / (1024.0 * 1024.0); 355 - let limit_mb = MAX_SITE_SIZE as f64 / (1024.0 * 1024.0); 356 - println!(" • Total size: {:.1} MB (limit: {:.0} MB)", size_mb, limit_mb); 357 - } 358 - 359 - println!("\nwisp.place will NOT serve your site if you proceed."); 360 - println!("Your site will be uploaded to your PDS, but will only be accessible via:"); 361 - println!(" • wisp-cli serve (local hosting)"); 362 - println!(" • Other hosting services with more generous limits"); 363 - 364 - if !skip_prompts { 365 - // Prompt for confirmation 366 - use std::io::{self, Write}; 367 - print!("\nDo you want to upload anyway? (y/N): "); 368 - io::stdout().flush().into_diagnostic()?; 369 - 370 - let mut input = String::new(); 371 - io::stdin().read_line(&mut input).into_diagnostic()?; 372 - let input = input.trim().to_lowercase(); 373 - 374 - if input != "y" && input != "yes" { 375 - println!("Upload cancelled."); 376 - return Ok(()); 377 - } 378 - } else { 379 - println!("\nSkipping confirmation (--yes flag set)."); 380 - } 381 - 382 - println!("\nProceeding with upload...\n"); 383 - } 384 - 385 - // Try to fetch existing manifest for incremental updates 386 - let (existing_blob_map, old_subfs_uris): (HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, Vec<(String, String)>) = { 387 - use jacquard_common::types::string::AtUri; 388 - 389 - // Get the DID for this session 390 - let session_info = agent.session_info().await; 391 - if let Some((did, _)) = session_info { 392 - // Construct the AT URI for the record 393 - let uri_string = format!("at://{}/place.wisp.fs/{}", did, site_name); 394 - if let Ok(uri) = AtUri::new(&uri_string) { 395 - match agent.get_record::<Fs>(&uri).await { 396 - Ok(response) => { 397 - match response.into_output() { 398 - Ok(record_output) => { 399 - let existing_manifest = record_output.value; 400 - let mut blob_map = blob_map::extract_blob_map(&existing_manifest.root); 401 - println!("Found existing manifest with {} files in main record", blob_map.len()); 402 - 403 - // Extract subfs URIs from main record 404 - let subfs_uris = subfs_utils::extract_subfs_uris(&existing_manifest.root, String::new()); 405 - 406 - if !subfs_uris.is_empty() { 407 - println!("Found {} subfs records, fetching for blob reuse...", subfs_uris.len()); 408 - 409 - // Merge blob maps from all subfs records 410 - match subfs_utils::merge_subfs_blob_maps(agent, subfs_uris.clone(), &mut blob_map).await { 411 - Ok(merged_count) => { 412 - println!("Total blob map: {} files (main + {} from subfs)", blob_map.len(), merged_count); 413 - } 414 - Err(e) => { 415 - eprintln!("⚠️ Failed to merge some subfs blob maps: {}", e); 416 - } 417 - } 418 - 419 - (blob_map, subfs_uris) 420 - } else { 421 - (blob_map, Vec::new()) 422 - } 423 - } 424 - Err(_) => { 425 - println!("No existing manifest found, uploading all files..."); 426 - (HashMap::new(), Vec::new()) 427 - } 428 - } 429 - } 430 - Err(_) => { 431 - // Record doesn't exist yet - this is a new site 432 - println!("No existing manifest found, uploading all files..."); 433 - (HashMap::new(), Vec::new()) 434 - } 435 - } 436 - } else { 437 - println!("No existing manifest found (invalid URI), uploading all files..."); 438 - (HashMap::new(), Vec::new()) 439 - } 440 - } else { 441 - println!("No existing manifest found (could not get DID), uploading all files..."); 442 - (HashMap::new(), Vec::new()) 443 - } 444 - }; 445 - 446 - // Create progress tracking (spinner style since we don't know total count upfront) 447 - let multi_progress = MultiProgress::new(); 448 - let progress = multi_progress.add(ProgressBar::new_spinner()); 449 - progress.set_style( 450 - ProgressStyle::default_spinner() 451 - .template("[{elapsed_precise}] {spinner:.cyan} {pos} files {msg}") 452 - .into_diagnostic()? 453 - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ") 454 - ); 455 - progress.set_message("Scanning files..."); 456 - progress.enable_steady_tick(std::time::Duration::from_millis(100)); 457 - 458 - let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher, &progress).await?; 459 - let uploaded_count = total_files - reused_count; 460 - 461 - progress.finish_with_message(format!("✓ {} files ({} uploaded, {} reused)", total_files, uploaded_count, reused_count)); 462 - 463 - // Check if we need to split into subfs records 464 - const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB) 465 - const FILE_COUNT_THRESHOLD: usize = 250; // Start splitting at this many files 466 - const TARGET_FILE_COUNT: usize = 200; // Keep main manifest under this 467 - 468 - let mut working_directory = root_dir; 469 - let mut current_file_count = total_files; 470 - let mut new_subfs_uris: Vec<(String, String)> = Vec::new(); 471 - 472 - // Estimate initial manifest size 473 - let mut manifest_size = subfs_utils::estimate_directory_size(&working_directory); 474 - 475 - if total_files >= FILE_COUNT_THRESHOLD || manifest_size > MAX_MANIFEST_SIZE { 476 - println!("\n⚠️ Large site detected ({} files, {:.1}KB manifest), splitting into subfs records...", 477 - total_files, manifest_size as f64 / 1024.0); 478 - 479 - let mut attempts = 0; 480 - const MAX_SPLIT_ATTEMPTS: usize = 50; 481 - 482 - while (manifest_size > MAX_MANIFEST_SIZE || current_file_count > TARGET_FILE_COUNT) && attempts < MAX_SPLIT_ATTEMPTS { 483 - attempts += 1; 484 - 485 - // Find large directories to split 486 - let directories = subfs_utils::find_large_directories(&working_directory, String::new()); 487 - 488 - if let Some(largest_dir) = directories.first() { 489 - println!(" Split #{}: {} ({} files, {:.1}KB)", 490 - attempts, largest_dir.path, largest_dir.file_count, largest_dir.size as f64 / 1024.0); 491 - 492 - // Check if this directory is itself too large for a single subfs record 493 - const MAX_SUBFS_SIZE: usize = 75 * 1024; // 75KB soft limit for safety 494 - let mut subfs_uri = String::new(); 495 - 496 - if largest_dir.size > MAX_SUBFS_SIZE { 497 - // Need to split this directory into multiple chunks 498 - println!(" → Directory too large, splitting into chunks..."); 499 - let chunks = subfs_utils::split_directory_into_chunks(&largest_dir.directory, MAX_SUBFS_SIZE); 500 - println!(" → Created {} chunks", chunks.len()); 501 - 502 - // Upload each chunk as a subfs record 503 - let mut chunk_uris = Vec::new(); 504 - for (i, chunk) in chunks.iter().enumerate() { 505 - use jacquard_common::types::string::Tid; 506 - let chunk_tid = Tid::now_0(); 507 - let chunk_rkey = chunk_tid.to_string(); 508 - 509 - let chunk_file_count = subfs_utils::count_files_in_directory(chunk); 510 - let chunk_size = subfs_utils::estimate_directory_size(chunk); 511 - 512 - let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 513 - .root(convert_fs_dir_to_subfs_dir(chunk.clone())) 514 - .file_count(Some(chunk_file_count as i64)) 515 - .created_at(Datetime::now()) 516 - .build(); 517 - 518 - println!(" → Uploading chunk {}/{} ({} files, {:.1}KB)...", 519 - i + 1, chunks.len(), chunk_file_count, chunk_size as f64 / 1024.0); 520 - 521 - let chunk_output = agent.put_record( 522 - RecordKey::from(Rkey::new(&chunk_rkey).into_diagnostic()?), 523 - chunk_manifest 524 - ).await.into_diagnostic()?; 525 - 526 - let chunk_uri = chunk_output.uri.to_string(); 527 - chunk_uris.push((chunk_uri.clone(), format!("{}#{}", largest_dir.path, i))); 528 - new_subfs_uris.push((chunk_uri.clone(), format!("{}#{}", largest_dir.path, i))); 529 - } 530 - 531 - // Create a parent subfs record that references all chunks 532 - // Each chunk reference MUST have flat: true to merge chunk contents 533 - println!(" → Creating parent subfs with {} chunk references...", chunk_uris.len()); 534 - use jacquard_common::CowStr; 535 - use wisp_lexicons::place_wisp::fs::{Subfs}; 536 - 537 - // Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs 538 - let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| { 539 - let uri_string = uri.clone(); 540 - let at_uri = AtUri::new_cow(CowStr::from(uri_string)).expect("valid URI"); 541 - Entry::new() 542 - .name(CowStr::from(format!("chunk{}", i))) 543 - .node(EntryNode::Subfs(Box::new( 544 - Subfs::new() 545 - .r#type(CowStr::from("subfs")) 546 - .subject(at_uri) 547 - .flat(Some(true)) // EXPLICITLY TRUE - merge chunk contents 548 - .build() 549 - ))) 550 - .build() 551 - }).collect(); 552 - 553 - let parent_root_fs = Directory::new() 554 - .r#type(CowStr::from("directory")) 555 - .entries(parent_entries_fs) 556 - .build(); 557 - 558 - // Convert to subfs::Directory for the parent subfs record 559 - let parent_root_subfs = convert_fs_dir_to_subfs_dir(parent_root_fs); 560 - 561 - use jacquard_common::types::string::Tid; 562 - let parent_tid = Tid::now_0(); 563 - let parent_rkey = parent_tid.to_string(); 564 - 565 - let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 566 - .root(parent_root_subfs) 567 - .file_count(Some(largest_dir.file_count as i64)) 568 - .created_at(Datetime::now()) 569 - .build(); 570 - 571 - let parent_output = agent.put_record( 572 - RecordKey::from(Rkey::new(&parent_rkey).into_diagnostic()?), 573 - parent_manifest 574 - ).await.into_diagnostic()?; 575 - 576 - subfs_uri = parent_output.uri.to_string(); 577 - println!(" ✅ Created parent subfs with chunks (flat=true on each chunk): {}", subfs_uri); 578 - } else { 579 - // Directory fits in a single subfs record 580 - use jacquard_common::types::string::Tid; 581 - let subfs_tid = Tid::now_0(); 582 - let subfs_rkey = subfs_tid.to_string(); 583 - 584 - let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 585 - .root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone())) 586 - .file_count(Some(largest_dir.file_count as i64)) 587 - .created_at(Datetime::now()) 588 - .build(); 589 - 590 - // Upload subfs record 591 - let subfs_output = agent.put_record( 592 - RecordKey::from(Rkey::new(&subfs_rkey).into_diagnostic()?), 593 - subfs_manifest 594 - ).await.into_diagnostic()?; 595 - 596 - subfs_uri = subfs_output.uri.to_string(); 597 - println!(" ✅ Created subfs: {}", subfs_uri); 598 - } 599 - 600 - // Replace directory with subfs node (flat: false to preserve directory structure) 601 - working_directory = subfs_utils::replace_directory_with_subfs( 602 - working_directory, 603 - &largest_dir.path, 604 - &subfs_uri, 605 - false // Preserve directory - the chunks inside have flat=true 606 - )?; 607 - 608 - new_subfs_uris.push((subfs_uri, largest_dir.path.clone())); 609 - current_file_count -= largest_dir.file_count; 610 - 611 - // Recalculate manifest size 612 - manifest_size = subfs_utils::estimate_directory_size(&working_directory); 613 - println!(" → Manifest now {:.1}KB with {} files ({} subfs total)", 614 - manifest_size as f64 / 1024.0, current_file_count, new_subfs_uris.len()); 615 - 616 - if manifest_size <= MAX_MANIFEST_SIZE && current_file_count <= TARGET_FILE_COUNT { 617 - println!("✅ Manifest now fits within limits"); 618 - break; 619 - } 620 - } else { 621 - println!(" No more subdirectories to split - stopping"); 622 - break; 623 - } 624 - } 625 - 626 - if attempts >= MAX_SPLIT_ATTEMPTS { 627 - return Err(miette::miette!( 628 - "Exceeded maximum split attempts ({}). Manifest still too large: {:.1}KB with {} files", 629 - MAX_SPLIT_ATTEMPTS, 630 - manifest_size as f64 / 1024.0, 631 - current_file_count 632 - )); 633 - } 634 - 635 - println!("✅ Split complete: {} subfs records, {} files in main manifest, {:.1}KB", 636 - new_subfs_uris.len(), current_file_count, manifest_size as f64 / 1024.0); 637 - } else { 638 - println!("Manifest created ({} files, {:.1}KB) - no splitting needed", 639 - total_files, manifest_size as f64 / 1024.0); 640 - } 641 - 642 - // Create the final Fs record 643 - let fs_record = Fs::new() 644 - .site(CowStr::from(site_name.clone())) 645 - .root(working_directory) 646 - .file_count(current_file_count as i64) 647 - .created_at(Datetime::now()) 648 - .build(); 649 - 650 - // Use site name as the record key 651 - let rkey = Rkey::new(&site_name).map_err(|e| miette::miette!("Invalid rkey: {}", e))?; 652 - let output = agent.put_record(RecordKey::from(rkey), fs_record).await?; 653 - 654 - // Extract DID from the AT URI (format: at://did:plc:xxx/collection/rkey) 655 - let uri_str = output.uri.to_string(); 656 - let did = uri_str 657 - .strip_prefix("at://") 658 - .and_then(|s| s.split('/').next()) 659 - .ok_or_else(|| miette::miette!("Failed to parse DID from URI"))?; 660 - 661 - println!("\n✓ Deployed site '{}': {}", site_name, output.uri); 662 - println!(" Total files: {} ({} reused, {} uploaded)", total_files, reused_count, uploaded_count); 663 - println!(" Available at: https://sites.wisp.place/{}/{}", did, site_name); 664 - 665 - // Clean up old subfs records 666 - if !old_subfs_uris.is_empty() { 667 - println!("\nCleaning up {} old subfs records...", old_subfs_uris.len()); 668 - 669 - let mut deleted_count = 0; 670 - let mut failed_count = 0; 671 - 672 - for (uri, _path) in old_subfs_uris { 673 - match subfs_utils::delete_subfs_record(agent, &uri).await { 674 - Ok(_) => { 675 - deleted_count += 1; 676 - println!(" 🗑️ Deleted old subfs: {}", uri); 677 - } 678 - Err(e) => { 679 - failed_count += 1; 680 - eprintln!(" ⚠️ Failed to delete {}: {}", uri, e); 681 - } 682 - } 683 - } 684 - 685 - if failed_count > 0 { 686 - eprintln!("⚠️ Cleanup completed with {} deleted, {} failed", deleted_count, failed_count); 687 - } else { 688 - println!("✅ Cleanup complete: {} old subfs records deleted", deleted_count); 689 - } 690 - } 691 - 692 - // Upload settings if either flag is set 693 - if directory_listing || spa_mode { 694 - // Validate mutual exclusivity 695 - if directory_listing && spa_mode { 696 - return Err(miette::miette!("Cannot enable both --directory and --SPA modes")); 697 - } 698 - 699 - println!("\n⚙️ Uploading site settings..."); 700 - 701 - // Build settings record 702 - let mut settings_builder = Settings::new(); 703 - 704 - if directory_listing { 705 - settings_builder = settings_builder.directory_listing(Some(true)); 706 - println!(" • Directory listing: enabled"); 707 - } 708 - 709 - if spa_mode { 710 - settings_builder = settings_builder.spa_mode(Some(CowStr::from("index.html"))); 711 - println!(" • SPA mode: enabled (serving index.html for all routes)"); 712 - } 713 - 714 - let settings_record = settings_builder.build(); 715 - 716 - // Upload settings record with same rkey as site 717 - let rkey = Rkey::new(&site_name).map_err(|e| miette::miette!("Invalid rkey: {}", e))?; 718 - match agent.put_record(RecordKey::from(rkey), settings_record).await { 719 - Ok(settings_output) => { 720 - println!("✅ Settings uploaded: {}", settings_output.uri); 721 - } 722 - Err(e) => { 723 - eprintln!("⚠️ Failed to upload settings: {}", e); 724 - eprintln!(" Site was deployed successfully, but settings may need to be configured manually."); 725 - } 726 - } 727 - } 728 - 729 - Ok(()) 730 - } 731 - 732 - /// Recursively build a Directory from a filesystem path 733 - /// current_path is the path from the root of the site (e.g., "" for root, "config" for config dir) 734 - fn build_directory<'a>( 735 - agent: &'a Agent<impl jacquard::client::AgentSession + IdentityResolver + 'a>, 736 - dir_path: &'a Path, 737 - existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 738 - current_path: String, 739 - ignore_matcher: &'a ignore_patterns::IgnoreMatcher, 740 - progress: &'a ProgressBar, 741 - ) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>> 742 - { 743 - Box::pin(async move { 744 - // Collect all directory entries first 745 - let dir_entries: Vec<_> = std::fs::read_dir(dir_path) 746 - .into_diagnostic()? 747 - .collect::<Result<Vec<_>, _>>() 748 - .into_diagnostic()?; 749 - 750 - // Separate files and directories 751 - let mut file_tasks = Vec::new(); 752 - let mut dir_tasks = Vec::new(); 753 - 754 - for entry in dir_entries { 755 - let path = entry.path(); 756 - let name = entry.file_name(); 757 - let name_str = name.to_str() 758 - .ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))? 759 - .to_string(); 760 - 761 - // Construct full path for ignore checking 762 - let full_path = if current_path.is_empty() { 763 - name_str.clone() 764 - } else { 765 - format!("{}/{}", current_path, name_str) 766 - }; 767 - 768 - // Skip files/directories that match ignore patterns 769 - if ignore_matcher.is_ignored(&full_path) || ignore_matcher.is_filename_ignored(&name_str) { 770 - continue; 771 - } 772 - 773 - let metadata = entry.metadata().into_diagnostic()?; 774 - 775 - if metadata.is_file() { 776 - // Construct full path for this file (for blob map lookup) 777 - let full_path = if current_path.is_empty() { 778 - name_str.clone() 779 - } else { 780 - format!("{}/{}", current_path, name_str) 781 - }; 782 - file_tasks.push((name_str, path, full_path)); 783 - } else if metadata.is_dir() { 784 - dir_tasks.push((name_str, path)); 785 - } 786 - } 787 - 788 - // Process files concurrently with a limit of 2 789 - let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks) 790 - .map(|(name, path, full_path)| async move { 791 - let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs, progress).await?; 792 - progress.inc(1); 793 - let entry = Entry::new() 794 - .name(CowStr::from(name)) 795 - .node(EntryNode::File(Box::new(file_node))) 796 - .build(); 797 - Ok::<_, miette::Report>((entry, reused)) 798 - }) 799 - .buffer_unordered(MAX_CONCURRENT_UPLOADS) 800 - .collect::<Vec<_>>() 801 - .await 802 - .into_iter() 803 - .collect::<miette::Result<Vec<_>>>()?; 804 - 805 - let mut file_entries = Vec::new(); 806 - let mut reused_count = 0; 807 - let mut total_files = 0; 808 - 809 - for (entry, reused) in file_results { 810 - file_entries.push(entry); 811 - total_files += 1; 812 - if reused { 813 - reused_count += 1; 814 - } 815 - } 816 - 817 - // Process directories recursively (sequentially to avoid too much nesting) 818 - let mut dir_entries = Vec::new(); 819 - for (name, path) in dir_tasks { 820 - // Construct full path for subdirectory 821 - let subdir_path = if current_path.is_empty() { 822 - name.clone() 823 - } else { 824 - format!("{}/{}", current_path, name) 825 - }; 826 - let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher, progress).await?; 827 - dir_entries.push(Entry::new() 828 - .name(CowStr::from(name)) 829 - .node(EntryNode::Directory(Box::new(subdir))) 830 - .build()); 831 - total_files += sub_total; 832 - reused_count += sub_reused; 833 - } 834 - 835 - // Combine file and directory entries 836 - let mut entries = file_entries; 837 - entries.extend(dir_entries); 838 - 839 - let directory = Directory::new() 840 - .r#type(CowStr::from("directory")) 841 - .entries(entries) 842 - .build(); 843 - 844 - Ok((directory, total_files, reused_count)) 845 - }) 846 - } 847 - 848 - /// Process a single file: gzip -> base64 -> upload blob (or reuse existing) 849 - /// Returns (File, reused: bool) 850 - /// file_path_key is the full path from the site root (e.g., "config/file.json") for blob map lookup 851 - /// 852 - /// Special handling: _redirects files are NOT compressed (uploaded as-is) 853 - async fn process_file( 854 - agent: &Agent<impl jacquard::client::AgentSession + IdentityResolver>, 855 - file_path: &Path, 856 - file_path_key: &str, 857 - existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 858 - progress: &ProgressBar, 859 - ) -> miette::Result<(File<'static>, bool)> 860 - { 861 - // Read file 862 - let file_data = std::fs::read(file_path).into_diagnostic()?; 863 - 864 - // Detect original MIME type 865 - let original_mime = mime_guess::from_path(file_path) 866 - .first_or_octet_stream() 867 - .to_string(); 868 - 869 - // Check if this is a _redirects file (don't compress it) 870 - let is_redirects_file = file_path.file_name() 871 - .and_then(|n| n.to_str()) 872 - .map(|n| n == "_redirects") 873 - .unwrap_or(false); 874 - 875 - let (upload_bytes, encoding, is_base64) = if is_redirects_file { 876 - // Don't compress _redirects - upload as-is 877 - (file_data.clone(), None, false) 878 - } else { 879 - // Gzip compress 880 - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); 881 - encoder.write_all(&file_data).into_diagnostic()?; 882 - let gzipped = encoder.finish().into_diagnostic()?; 883 - 884 - // Base64 encode the gzipped data 885 - let base64_bytes = base64::prelude::BASE64_STANDARD.encode(&gzipped).into_bytes(); 886 - (base64_bytes, Some("gzip"), true) 887 - }; 888 - 889 - // Compute CID for this file 890 - let file_cid = cid::compute_cid(&upload_bytes); 891 - 892 - // Check if we have an existing blob with the same CID 893 - let existing_blob = existing_blobs.get(file_path_key); 894 - 895 - if let Some((existing_blob_ref, existing_cid)) = existing_blob { 896 - if existing_cid == &file_cid { 897 - // CIDs match - reuse existing blob 898 - progress.set_message(format!("✓ Reused {}", file_path_key)); 899 - let mut file_builder = File::new() 900 - .r#type(CowStr::from("file")) 901 - .blob(existing_blob_ref.clone()) 902 - .mime_type(CowStr::from(original_mime)); 903 - 904 - if let Some(enc) = encoding { 905 - file_builder = file_builder.encoding(CowStr::from(enc)); 906 - } 907 - if is_base64 { 908 - file_builder = file_builder.base64(true); 909 - } 910 - 911 - return Ok((file_builder.build(), true)); 912 - } 913 - } 914 - 915 - // File is new or changed - upload it 916 - let mime_type = if is_redirects_file { 917 - MimeType::new_static("text/plain") 918 - } else { 919 - MimeType::new_static("application/octet-stream") 920 - }; 921 - 922 - // Format file size nicely 923 - let size_str = if upload_bytes.len() < 1024 { 924 - format!("{} B", upload_bytes.len()) 925 - } else if upload_bytes.len() < 1024 * 1024 { 926 - format!("{:.1} KB", upload_bytes.len() as f64 / 1024.0) 927 - } else { 928 - format!("{:.1} MB", upload_bytes.len() as f64 / (1024.0 * 1024.0)) 929 - }; 930 - 931 - progress.set_message(format!("↑ Uploading {} ({})", file_path_key, size_str)); 932 - let blob = agent.upload_blob(upload_bytes, mime_type).await?; 933 - progress.set_message(format!("✓ Uploaded {}", file_path_key)); 934 - 935 - let mut file_builder = File::new() 936 - .r#type(CowStr::from("file")) 937 - .blob(blob) 938 - .mime_type(CowStr::from(original_mime)); 939 - 940 - if let Some(enc) = encoding { 941 - file_builder = file_builder.encoding(CowStr::from(enc)); 942 - } 943 - if is_base64 { 944 - file_builder = file_builder.base64(true); 945 - } 946 - 947 - Ok((file_builder.build(), false)) 948 - } 949 - 950 - /// Convert fs::Directory to subfs::Directory 951 - /// They have the same structure, but different types 952 - fn convert_fs_dir_to_subfs_dir(fs_dir: wisp_lexicons::place_wisp::fs::Directory<'static>) -> wisp_lexicons::place_wisp::subfs::Directory<'static> { 953 - use wisp_lexicons::place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile}; 954 - 955 - let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| { 956 - let node = match entry.node { 957 - wisp_lexicons::place_wisp::fs::EntryNode::File(file) => { 958 - SubfsEntryNode::File(Box::new(SubfsFile::new() 959 - .r#type(file.r#type) 960 - .blob(file.blob) 961 - .encoding(file.encoding) 962 - .mime_type(file.mime_type) 963 - .base64(file.base64) 964 - .build())) 965 - } 966 - wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => { 967 - SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir))) 968 - } 969 - wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => { 970 - // Nested subfs in the directory we're converting 971 - // Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs 972 - SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new() 973 - .r#type(subfs.r#type) 974 - .subject(subfs.subject) 975 - .build())) 976 - } 977 - wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => { 978 - SubfsEntryNode::Unknown(unknown) 979 - } 980 - }; 981 - 982 - SubfsEntry::new() 983 - .name(entry.name) 984 - .node(node) 985 - .build() 986 - }).collect(); 987 - 988 - SubfsDirectory::new() 989 - .r#type(fs_dir.r#type) 990 - .entries(subfs_entries) 991 - .build() 992 - } 993 -
-46
rust-cli/src/metadata.rs
··· 1 - use serde::{Deserialize, Serialize}; 2 - use std::collections::HashMap; 3 - use std::path::Path; 4 - use miette::IntoDiagnostic; 5 - 6 - /// Metadata tracking file CIDs for incremental updates 7 - #[derive(Debug, Clone, Serialize, Deserialize)] 8 - pub struct SiteMetadata { 9 - /// Record CID from the PDS 10 - pub record_cid: String, 11 - /// Map of file paths to their blob CIDs 12 - pub file_cids: HashMap<String, String>, 13 - /// Timestamp when the site was last synced 14 - pub last_sync: i64, 15 - } 16 - 17 - impl SiteMetadata { 18 - pub fn new(record_cid: String, file_cids: HashMap<String, String>) -> Self { 19 - Self { 20 - record_cid, 21 - file_cids, 22 - last_sync: chrono::Utc::now().timestamp(), 23 - } 24 - } 25 - 26 - /// Load metadata from a directory 27 - pub fn load(dir: &Path) -> miette::Result<Option<Self>> { 28 - let metadata_path = dir.join(".wisp-metadata.json"); 29 - if !metadata_path.exists() { 30 - return Ok(None); 31 - } 32 - 33 - let contents = std::fs::read_to_string(&metadata_path).into_diagnostic()?; 34 - let metadata: SiteMetadata = serde_json::from_str(&contents).into_diagnostic()?; 35 - Ok(Some(metadata)) 36 - } 37 - 38 - /// Save metadata to a directory 39 - pub fn save(&self, dir: &Path) -> miette::Result<()> { 40 - let metadata_path = dir.join(".wisp-metadata.json"); 41 - let contents = serde_json::to_string_pretty(self).into_diagnostic()?; 42 - std::fs::write(&metadata_path, contents).into_diagnostic()?; 43 - Ok(()) 44 - } 45 - } 46 -
-681
rust-cli/src/pull.rs
··· 1 - use crate::blob_map; 2 - use crate::download; 3 - use crate::metadata::SiteMetadata; 4 - use wisp_lexicons::place_wisp::fs::*; 5 - use crate::subfs_utils; 6 - use jacquard::CowStr; 7 - use jacquard::prelude::IdentityResolver; 8 - use jacquard_common::types::string::Did; 9 - use jacquard_common::xrpc::XrpcExt; 10 - use jacquard_identity::PublicResolver; 11 - use miette::IntoDiagnostic; 12 - use std::collections::HashMap; 13 - use std::path::{Path, PathBuf}; 14 - use url::Url; 15 - 16 - /// Pull a site from the PDS to a local directory 17 - pub async fn pull_site( 18 - input: CowStr<'static>, 19 - rkey: CowStr<'static>, 20 - output_dir: PathBuf, 21 - ) -> miette::Result<()> { 22 - println!("Pulling site {} from {}...", rkey, input); 23 - 24 - // Resolve handle to DID if needed 25 - let resolver = PublicResolver::default(); 26 - let did = if input.starts_with("did:") { 27 - Did::new(&input).into_diagnostic()? 28 - } else { 29 - // It's a handle, resolve it 30 - let handle = jacquard_common::types::string::Handle::new(&input).into_diagnostic()?; 31 - resolver.resolve_handle(&handle).await.into_diagnostic()? 32 - }; 33 - 34 - // Resolve PDS endpoint for the DID 35 - let pds_url = resolver.pds_for_did(&did).await.into_diagnostic()?; 36 - println!("Resolved PDS: {}", pds_url); 37 - 38 - // Create a temporary agent for fetching records (no auth needed for public reads) 39 - println!("Fetching record from PDS..."); 40 - let client = reqwest::Client::new(); 41 - 42 - // Use com.atproto.repo.getRecord 43 - use jacquard::api::com_atproto::repo::get_record::GetRecord; 44 - use jacquard_common::types::string::Rkey as RkeyType; 45 - let rkey_parsed = RkeyType::new(&rkey).into_diagnostic()?; 46 - 47 - use jacquard_common::types::ident::AtIdentifier; 48 - use jacquard_common::types::string::RecordKey; 49 - let request = GetRecord::new() 50 - .repo(AtIdentifier::Did(did.clone())) 51 - .collection(CowStr::from("place.wisp.fs")) 52 - .rkey(RecordKey::from(rkey_parsed)) 53 - .build(); 54 - 55 - let response = client 56 - .xrpc(pds_url.clone()) 57 - .send(&request) 58 - .await 59 - .into_diagnostic()?; 60 - 61 - let record_output = response.into_output().into_diagnostic()?; 62 - let record_cid = record_output.cid.as_ref().map(|c| c.to_string()).unwrap_or_default(); 63 - 64 - // Parse the record value as Fs 65 - use jacquard_common::types::value::from_data; 66 - let fs_record: Fs = from_data(&record_output.value).into_diagnostic()?; 67 - 68 - let file_count = fs_record.file_count.map(|c| c.to_string()).unwrap_or_else(|| "?".to_string()); 69 - println!("Found site '{}' with {} files (in main record)", fs_record.site, file_count); 70 - 71 - // Check for and expand subfs nodes 72 - // Note: We use a custom expand function for pull since we don't have an Agent 73 - let expanded_root = expand_subfs_in_pull_with_client(&fs_record.root, &client, &pds_url).await?; 74 - let total_file_count = subfs_utils::count_files_in_directory(&expanded_root); 75 - 76 - if total_file_count as i64 != fs_record.file_count.unwrap_or(0) { 77 - println!("Total files after expanding subfs: {}", total_file_count); 78 - } 79 - 80 - // Load existing metadata for incremental updates 81 - let existing_metadata = SiteMetadata::load(&output_dir)?; 82 - let existing_file_cids = existing_metadata 83 - .as_ref() 84 - .map(|m| m.file_cids.clone()) 85 - .unwrap_or_default(); 86 - 87 - // Extract blob map from the expanded manifest 88 - let new_blob_map = blob_map::extract_blob_map(&expanded_root); 89 - let new_file_cids: HashMap<String, String> = new_blob_map 90 - .iter() 91 - .map(|(path, (_blob_ref, cid))| (path.clone(), cid.clone())) 92 - .collect(); 93 - 94 - // Clean up any leftover temp directories from previous failed attempts 95 - let parent = output_dir.parent().unwrap_or_else(|| std::path::Path::new(".")); 96 - let output_name = output_dir.file_name().unwrap_or_else(|| std::ffi::OsStr::new("site")).to_string_lossy(); 97 - let temp_prefix = format!(".tmp-{}-", output_name); 98 - 99 - if let Ok(entries) = parent.read_dir() { 100 - for entry in entries.flatten() { 101 - let name = entry.file_name(); 102 - if name.to_string_lossy().starts_with(&temp_prefix) { 103 - let _ = std::fs::remove_dir_all(entry.path()); 104 - } 105 - } 106 - } 107 - 108 - // Check if we need to update (verify files actually exist, not just metadata) 109 - if let Some(metadata) = &existing_metadata { 110 - if metadata.record_cid == record_cid { 111 - // Verify that the output directory actually exists and has the expected files 112 - let has_all_files = output_dir.exists() && { 113 - // Count actual files on disk (excluding metadata) 114 - let mut actual_file_count = 0; 115 - if let Ok(entries) = std::fs::read_dir(&output_dir) { 116 - for entry in entries.flatten() { 117 - let name = entry.file_name(); 118 - if !name.to_string_lossy().starts_with(".wisp-metadata") { 119 - if entry.path().is_file() { 120 - actual_file_count += 1; 121 - } 122 - } 123 - } 124 - } 125 - 126 - // Compare with expected file count from metadata 127 - let expected_count = metadata.file_cids.len(); 128 - actual_file_count > 0 && actual_file_count >= expected_count 129 - }; 130 - 131 - if has_all_files { 132 - println!("Site is already up to date!"); 133 - return Ok(()); 134 - } else { 135 - println!("Site metadata exists but files are missing, re-downloading..."); 136 - } 137 - } 138 - } 139 - 140 - // Create temporary directory for atomic update 141 - // Place temp dir in parent directory to avoid issues with non-existent output_dir 142 - let parent = output_dir.parent().unwrap_or_else(|| std::path::Path::new(".")); 143 - let temp_dir_name = format!( 144 - ".tmp-{}-{}", 145 - output_dir.file_name().unwrap_or_else(|| std::ffi::OsStr::new("site")).to_string_lossy(), 146 - chrono::Utc::now().timestamp() 147 - ); 148 - let temp_dir = parent.join(temp_dir_name); 149 - std::fs::create_dir_all(&temp_dir).into_diagnostic()?; 150 - 151 - println!("Downloading files..."); 152 - let mut downloaded = 0; 153 - let mut reused = 0; 154 - 155 - // Download files recursively (using expanded root) 156 - let download_result = download_directory( 157 - &expanded_root, 158 - &temp_dir, 159 - &pds_url, 160 - did.as_str(), 161 - &new_blob_map, 162 - &existing_file_cids, 163 - &output_dir, 164 - String::new(), 165 - &mut downloaded, 166 - &mut reused, 167 - ) 168 - .await; 169 - 170 - // If download failed, clean up temp directory 171 - if let Err(e) = download_result { 172 - let _ = std::fs::remove_dir_all(&temp_dir); 173 - return Err(e); 174 - } 175 - 176 - println!( 177 - "Downloaded {} files, reused {} files", 178 - downloaded, reused 179 - ); 180 - 181 - // Save metadata 182 - let metadata = SiteMetadata::new(record_cid, new_file_cids); 183 - metadata.save(&temp_dir)?; 184 - 185 - // Move files from temp to output directory 186 - let output_abs = std::fs::canonicalize(&output_dir).unwrap_or_else(|_| output_dir.clone()); 187 - let current_dir = std::env::current_dir().into_diagnostic()?; 188 - 189 - // Special handling for pulling to current directory 190 - if output_abs == current_dir { 191 - // Move files from temp to current directory 192 - for entry in std::fs::read_dir(&temp_dir).into_diagnostic()? { 193 - let entry = entry.into_diagnostic()?; 194 - let dest = current_dir.join(entry.file_name()); 195 - 196 - // Remove existing file/dir if it exists 197 - if dest.exists() { 198 - if dest.is_dir() { 199 - std::fs::remove_dir_all(&dest).into_diagnostic()?; 200 - } else { 201 - std::fs::remove_file(&dest).into_diagnostic()?; 202 - } 203 - } 204 - 205 - // Move from temp to current dir 206 - std::fs::rename(entry.path(), dest).into_diagnostic()?; 207 - } 208 - 209 - // Clean up temp directory 210 - std::fs::remove_dir_all(&temp_dir).into_diagnostic()?; 211 - } else { 212 - // If output directory exists and has content, remove it first 213 - if output_dir.exists() { 214 - std::fs::remove_dir_all(&output_dir).into_diagnostic()?; 215 - } 216 - 217 - // Ensure parent directory exists 218 - if let Some(parent) = output_dir.parent() { 219 - if !parent.as_os_str().is_empty() && !parent.exists() { 220 - std::fs::create_dir_all(parent).into_diagnostic()?; 221 - } 222 - } 223 - 224 - // Rename temp to final location 225 - match std::fs::rename(&temp_dir, &output_dir) { 226 - Ok(_) => {}, 227 - Err(e) => { 228 - // Clean up temp directory on failure 229 - let _ = std::fs::remove_dir_all(&temp_dir); 230 - return Err(miette::miette!("Failed to move temp directory: {}", e)); 231 - } 232 - } 233 - } 234 - 235 - println!("✓ Site pulled successfully to {}", output_dir.display()); 236 - 237 - Ok(()) 238 - } 239 - 240 - /// Recursively download a directory with concurrent downloads 241 - fn download_directory<'a>( 242 - dir: &'a Directory<'_>, 243 - output_dir: &'a Path, 244 - pds_url: &'a Url, 245 - did: &'a str, 246 - new_blob_map: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 247 - existing_file_cids: &'a HashMap<String, String>, 248 - existing_output_dir: &'a Path, 249 - path_prefix: String, 250 - downloaded: &'a mut usize, 251 - reused: &'a mut usize, 252 - ) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<()>> + Send + 'a>> { 253 - Box::pin(async move { 254 - use futures::stream::{self, StreamExt}; 255 - 256 - // Collect download tasks and directory tasks separately 257 - struct DownloadTask { 258 - path: String, 259 - output_path: PathBuf, 260 - blob: jacquard_common::types::blob::BlobRef<'static>, 261 - base64: bool, 262 - gzip: bool, 263 - } 264 - 265 - struct CopyTask { 266 - path: String, 267 - from: PathBuf, 268 - to: PathBuf, 269 - } 270 - 271 - let mut download_tasks = Vec::new(); 272 - let mut copy_tasks = Vec::new(); 273 - let mut dir_tasks = Vec::new(); 274 - 275 - for entry in &dir.entries { 276 - let entry_name = entry.name.as_str(); 277 - let current_path = if path_prefix.is_empty() { 278 - entry_name.to_string() 279 - } else { 280 - format!("{}/{}", path_prefix, entry_name) 281 - }; 282 - 283 - match &entry.node { 284 - EntryNode::File(file) => { 285 - let output_path = output_dir.join(entry_name); 286 - 287 - // Check if file CID matches existing 288 - let should_copy = if let Some((_blob_ref, new_cid)) = new_blob_map.get(&current_path) { 289 - if let Some(existing_cid) = existing_file_cids.get(&current_path) { 290 - if existing_cid == new_cid { 291 - let existing_path = existing_output_dir.join(&current_path); 292 - if existing_path.exists() { 293 - copy_tasks.push(CopyTask { 294 - path: current_path.clone(), 295 - from: existing_path, 296 - to: output_path.clone(), 297 - }); 298 - true 299 - } else { 300 - false 301 - } 302 - } else { 303 - false 304 - } 305 - } else { 306 - false 307 - } 308 - } else { 309 - false 310 - }; 311 - 312 - if !should_copy { 313 - use jacquard_common::IntoStatic; 314 - // File needs to be downloaded 315 - download_tasks.push(DownloadTask { 316 - path: current_path, 317 - output_path, 318 - blob: file.blob.clone().into_static(), 319 - base64: file.base64.unwrap_or(false), 320 - gzip: file.encoding.as_ref().map(|e| e.as_str() == "gzip").unwrap_or(false), 321 - }); 322 - } 323 - } 324 - EntryNode::Directory(subdir) => { 325 - let subdir_path = output_dir.join(entry_name); 326 - dir_tasks.push((subdir.as_ref().clone(), subdir_path, current_path)); 327 - } 328 - EntryNode::Subfs(_) => { 329 - println!(" ⚠ Skipping subfs node at {} (should have been expanded)", current_path); 330 - } 331 - EntryNode::Unknown(_) => { 332 - println!(" ⚠ Skipping unknown node type for {}", current_path); 333 - } 334 - } 335 - } 336 - 337 - // Execute copy tasks (fast, do them all) 338 - for task in copy_tasks { 339 - std::fs::copy(&task.from, &task.to).into_diagnostic()?; 340 - *reused += 1; 341 - println!(" ✓ Reused {}", task.path); 342 - } 343 - 344 - // Execute download tasks with concurrency limit (20 concurrent downloads) 345 - const DOWNLOAD_CONCURRENCY: usize = 20; 346 - 347 - let pds_url_clone = pds_url.clone(); 348 - let did_str = did.to_string(); 349 - 350 - let download_results: Vec<miette::Result<(String, PathBuf, Vec<u8>)>> = stream::iter(download_tasks) 351 - .map(|task| { 352 - let pds = pds_url_clone.clone(); 353 - let did_copy = did_str.clone(); 354 - 355 - async move { 356 - println!(" ↓ Downloading {}", task.path); 357 - let data = download::download_and_decompress_blob( 358 - &pds, 359 - &task.blob, 360 - &did_copy, 361 - task.base64, 362 - task.gzip, 363 - ) 364 - .await?; 365 - 366 - Ok::<_, miette::Report>((task.path, task.output_path, data)) 367 - } 368 - }) 369 - .buffer_unordered(DOWNLOAD_CONCURRENCY) 370 - .collect() 371 - .await; 372 - 373 - // Write downloaded files to disk 374 - for result in download_results { 375 - let (path, output_path, data) = result?; 376 - std::fs::write(&output_path, data).into_diagnostic()?; 377 - *downloaded += 1; 378 - println!(" ✓ Downloaded {}", path); 379 - } 380 - 381 - // Recursively process directories 382 - for (subdir, subdir_path, current_path) in dir_tasks { 383 - std::fs::create_dir_all(&subdir_path).into_diagnostic()?; 384 - 385 - download_directory( 386 - &subdir, 387 - &subdir_path, 388 - pds_url, 389 - did, 390 - new_blob_map, 391 - existing_file_cids, 392 - existing_output_dir, 393 - current_path, 394 - downloaded, 395 - reused, 396 - ) 397 - .await?; 398 - } 399 - 400 - Ok(()) 401 - }) 402 - } 403 - 404 - /// Expand subfs nodes in a directory tree by fetching and merging subfs records (RECURSIVELY) 405 - /// Uses reqwest client directly for pull command (no agent needed) 406 - async fn expand_subfs_in_pull_with_client<'a>( 407 - directory: &Directory<'a>, 408 - client: &reqwest::Client, 409 - pds_url: &Url, 410 - ) -> miette::Result<Directory<'static>> { 411 - use jacquard_common::IntoStatic; 412 - use jacquard_common::types::value::from_data; 413 - use wisp_lexicons::place_wisp::subfs::SubfsRecord; 414 - 415 - let mut all_subfs_map: HashMap<String, wisp_lexicons::place_wisp::subfs::Directory> = HashMap::new(); 416 - let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new()); 417 - 418 - if to_fetch.is_empty() { 419 - return Ok((*directory).clone().into_static()); 420 - } 421 - 422 - println!("Found {} subfs records, fetching recursively...", to_fetch.len()); 423 - 424 - let mut iteration = 0; 425 - const MAX_ITERATIONS: usize = 10; 426 - 427 - while !to_fetch.is_empty() && iteration < MAX_ITERATIONS { 428 - iteration += 1; 429 - println!(" Iteration {}: fetching {} subfs records...", iteration, to_fetch.len()); 430 - 431 - let mut fetch_tasks = Vec::new(); 432 - 433 - for (uri, path) in to_fetch.clone() { 434 - let client = client.clone(); 435 - let pds_url = pds_url.clone(); 436 - 437 - fetch_tasks.push(async move { 438 - // Parse URI 439 - let parts: Vec<&str> = uri.trim_start_matches("at://").split('/').collect(); 440 - if parts.len() < 3 { 441 - return Err(miette::miette!("Invalid subfs URI: {}", uri)); 442 - } 443 - 444 - let did_str = parts[0]; 445 - let collection = parts[1]; 446 - let rkey_str = parts[2]; 447 - 448 - if collection != "place.wisp.subfs" { 449 - return Err(miette::miette!("Expected place.wisp.subfs collection, got: {}", collection)); 450 - } 451 - 452 - // Fetch using GetRecord 453 - use jacquard::api::com_atproto::repo::get_record::GetRecord; 454 - use jacquard_common::types::string::{Rkey as RkeyType, Did as DidType, RecordKey}; 455 - use jacquard_common::types::ident::AtIdentifier; 456 - 457 - let rkey_parsed = RkeyType::new(rkey_str).into_diagnostic()?; 458 - let did_parsed = DidType::new(did_str).into_diagnostic()?; 459 - 460 - let request = GetRecord::new() 461 - .repo(AtIdentifier::Did(did_parsed)) 462 - .collection(CowStr::from("place.wisp.subfs")) 463 - .rkey(RecordKey::from(rkey_parsed)) 464 - .build(); 465 - 466 - let response = client 467 - .xrpc(pds_url) 468 - .send(&request) 469 - .await 470 - .into_diagnostic()?; 471 - 472 - let record_output = response.into_output().into_diagnostic()?; 473 - let subfs_record: SubfsRecord = from_data(&record_output.value).into_diagnostic()?; 474 - 475 - Ok::<_, miette::Report>((path, subfs_record.into_static())) 476 - }); 477 - } 478 - 479 - let results: Vec<_> = futures::future::join_all(fetch_tasks).await; 480 - 481 - // Process results and find nested subfs 482 - let mut newly_found_uris = Vec::new(); 483 - for result in results { 484 - match result { 485 - Ok((path, record)) => { 486 - println!(" ✓ Fetched subfs at {}", path); 487 - 488 - // Extract nested subfs URIs 489 - let nested_uris = extract_subfs_uris_from_subfs_dir(&record.root, path.clone()); 490 - newly_found_uris.extend(nested_uris); 491 - 492 - all_subfs_map.insert(path, record.root); 493 - } 494 - Err(e) => { 495 - eprintln!(" ⚠️ Failed to fetch subfs: {}", e); 496 - } 497 - } 498 - } 499 - 500 - // Filter out already-fetched paths 501 - to_fetch = newly_found_uris 502 - .into_iter() 503 - .filter(|(_, path)| !all_subfs_map.contains_key(path)) 504 - .collect(); 505 - } 506 - 507 - if iteration >= MAX_ITERATIONS { 508 - eprintln!("⚠️ Max iterations reached while fetching nested subfs"); 509 - } 510 - 511 - println!(" Total subfs records fetched: {}", all_subfs_map.len()); 512 - 513 - // Now replace all subfs nodes with their content 514 - Ok(replace_subfs_with_content(directory.clone(), &all_subfs_map, String::new())) 515 - } 516 - 517 - /// Extract subfs URIs from a subfs::Directory (helper for pull) 518 - fn extract_subfs_uris_from_subfs_dir( 519 - directory: &wisp_lexicons::place_wisp::subfs::Directory, 520 - current_path: String, 521 - ) -> Vec<(String, String)> { 522 - let mut uris = Vec::new(); 523 - 524 - for entry in &directory.entries { 525 - let full_path = if current_path.is_empty() { 526 - entry.name.to_string() 527 - } else { 528 - format!("{}/{}", current_path, entry.name) 529 - }; 530 - 531 - match &entry.node { 532 - wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => { 533 - uris.push((subfs_node.subject.to_string(), full_path.clone())); 534 - } 535 - wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => { 536 - let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path); 537 - uris.extend(nested); 538 - } 539 - _ => {} 540 - } 541 - } 542 - 543 - uris 544 - } 545 - 546 - /// Recursively replace subfs nodes with their actual content 547 - fn replace_subfs_with_content( 548 - directory: Directory, 549 - subfs_map: &HashMap<String, wisp_lexicons::place_wisp::subfs::Directory>, 550 - current_path: String, 551 - ) -> Directory<'static> { 552 - use jacquard_common::IntoStatic; 553 - 554 - let new_entries: Vec<Entry<'static>> = directory 555 - .entries 556 - .into_iter() 557 - .flat_map(|entry| { 558 - let full_path = if current_path.is_empty() { 559 - entry.name.to_string() 560 - } else { 561 - format!("{}/{}", current_path, entry.name) 562 - }; 563 - 564 - match entry.node { 565 - EntryNode::Subfs(subfs_node) => { 566 - // Check if we have this subfs record 567 - if let Some(subfs_dir) = subfs_map.get(&full_path) { 568 - let flat = subfs_node.flat.unwrap_or(true); // Default to flat merge 569 - 570 - if flat { 571 - // Flat merge: hoist subfs entries into parent 572 - println!(" Merging subfs {} (flat)", full_path); 573 - let converted_entries: Vec<Entry<'static>> = subfs_dir 574 - .entries 575 - .iter() 576 - .map(|subfs_entry| convert_subfs_entry_to_fs(subfs_entry.clone().into_static())) 577 - .collect(); 578 - 579 - converted_entries 580 - } else { 581 - // Nested: create a directory with the subfs name 582 - println!(" Merging subfs {} (nested)", full_path); 583 - let converted_entries: Vec<Entry<'static>> = subfs_dir 584 - .entries 585 - .iter() 586 - .map(|subfs_entry| convert_subfs_entry_to_fs(subfs_entry.clone().into_static())) 587 - .collect(); 588 - 589 - vec![Entry::new() 590 - .name(entry.name.into_static()) 591 - .node(EntryNode::Directory(Box::new( 592 - Directory::new() 593 - .r#type(CowStr::from("directory")) 594 - .entries(converted_entries) 595 - .build() 596 - ))) 597 - .build()] 598 - } 599 - } else { 600 - // Subfs not found, skip with warning 601 - eprintln!(" ⚠️ Subfs not found: {}", full_path); 602 - vec![] 603 - } 604 - } 605 - EntryNode::Directory(dir) => { 606 - // Recursively process subdirectories 607 - vec![Entry::new() 608 - .name(entry.name.into_static()) 609 - .node(EntryNode::Directory(Box::new( 610 - replace_subfs_with_content(*dir, subfs_map, full_path) 611 - ))) 612 - .build()] 613 - } 614 - EntryNode::File(_) => { 615 - vec![entry.into_static()] 616 - } 617 - EntryNode::Unknown(_) => { 618 - vec![entry.into_static()] 619 - } 620 - } 621 - }) 622 - .collect(); 623 - 624 - Directory::new() 625 - .r#type(CowStr::from("directory")) 626 - .entries(new_entries) 627 - .build() 628 - } 629 - 630 - /// Convert a subfs entry to a fs entry (they have the same structure but different types) 631 - fn convert_subfs_entry_to_fs(subfs_entry: wisp_lexicons::place_wisp::subfs::Entry<'static>) -> Entry<'static> { 632 - use jacquard_common::IntoStatic; 633 - 634 - let node = match subfs_entry.node { 635 - wisp_lexicons::place_wisp::subfs::EntryNode::File(file) => { 636 - EntryNode::File(Box::new( 637 - File::new() 638 - .r#type(file.r#type.into_static()) 639 - .blob(file.blob.into_static()) 640 - .encoding(file.encoding.map(|e| e.into_static())) 641 - .mime_type(file.mime_type.map(|m| m.into_static())) 642 - .base64(file.base64) 643 - .build() 644 - )) 645 - } 646 - wisp_lexicons::place_wisp::subfs::EntryNode::Directory(dir) => { 647 - let converted_entries: Vec<Entry<'static>> = dir 648 - .entries 649 - .into_iter() 650 - .map(|e| convert_subfs_entry_to_fs(e.into_static())) 651 - .collect(); 652 - 653 - EntryNode::Directory(Box::new( 654 - Directory::new() 655 - .r#type(dir.r#type.into_static()) 656 - .entries(converted_entries) 657 - .build() 658 - )) 659 - } 660 - wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => { 661 - // Nested subfs should have been expanded already - if we get here, it means expansion failed 662 - // Treat it like a directory reference that should have been expanded 663 - eprintln!(" ⚠️ Warning: unexpanded nested subfs at path, treating as empty directory"); 664 - EntryNode::Directory(Box::new( 665 - Directory::new() 666 - .r#type(CowStr::from("directory")) 667 - .entries(vec![]) 668 - .build() 669 - )) 670 - } 671 - wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(unknown) => { 672 - EntryNode::Unknown(unknown) 673 - } 674 - }; 675 - 676 - Entry::new() 677 - .name(subfs_entry.name.into_static()) 678 - .node(node) 679 - .build() 680 - } 681 -
-375
rust-cli/src/redirects.rs
··· 1 - use regex::Regex; 2 - use std::collections::HashMap; 3 - use std::fs; 4 - use std::path::Path; 5 - 6 - /// Maximum number of redirect rules to prevent DoS attacks 7 - const MAX_REDIRECT_RULES: usize = 1000; 8 - 9 - #[derive(Debug, Clone)] 10 - pub struct RedirectRule { 11 - #[allow(dead_code)] 12 - pub from: String, 13 - pub to: String, 14 - pub status: u16, 15 - #[allow(dead_code)] 16 - pub force: bool, 17 - pub from_pattern: Regex, 18 - pub from_params: Vec<String>, 19 - pub query_params: Option<HashMap<String, String>>, 20 - } 21 - 22 - #[derive(Debug)] 23 - pub struct RedirectMatch { 24 - pub target_path: String, 25 - pub status: u16, 26 - pub force: bool, 27 - } 28 - 29 - /// Parse a _redirects file into an array of redirect rules 30 - pub fn parse_redirects_file(content: &str) -> Vec<RedirectRule> { 31 - let lines = content.lines(); 32 - let mut rules = Vec::new(); 33 - 34 - for (line_num, line_raw) in lines.enumerate() { 35 - if line_raw.trim().is_empty() || line_raw.trim().starts_with('#') { 36 - continue; 37 - } 38 - 39 - // Enforce max rules limit 40 - if rules.len() >= MAX_REDIRECT_RULES { 41 - eprintln!( 42 - "Redirect rules limit reached ({}), ignoring remaining rules", 43 - MAX_REDIRECT_RULES 44 - ); 45 - break; 46 - } 47 - 48 - match parse_redirect_line(line_raw.trim()) { 49 - Ok(Some(rule)) => rules.push(rule), 50 - Ok(None) => continue, 51 - Err(e) => { 52 - eprintln!( 53 - "Failed to parse redirect rule on line {}: {} ({})", 54 - line_num + 1, 55 - line_raw, 56 - e 57 - ); 58 - } 59 - } 60 - } 61 - 62 - rules 63 - } 64 - 65 - /// Parse a single redirect rule line 66 - /// Format: /from [query_params] /to [status] [conditions] 67 - fn parse_redirect_line(line: &str) -> Result<Option<RedirectRule>, String> { 68 - let parts: Vec<&str> = line.split_whitespace().collect(); 69 - 70 - if parts.len() < 2 { 71 - return Ok(None); 72 - } 73 - 74 - let mut idx = 0; 75 - let from = parts[idx]; 76 - idx += 1; 77 - 78 - let mut status = 301; // Default status 79 - let mut force = false; 80 - let mut query_params: HashMap<String, String> = HashMap::new(); 81 - 82 - // Parse query parameters that come before the destination path 83 - while idx < parts.len() { 84 - let part = parts[idx]; 85 - 86 - // If it starts with / or http, it's the destination path 87 - if part.starts_with('/') || part.starts_with("http://") || part.starts_with("https://") { 88 - break; 89 - } 90 - 91 - // If it contains = and comes before the destination, it's a query param 92 - if part.contains('=') { 93 - let split_index = part.find('=').unwrap(); 94 - let key = &part[..split_index]; 95 - let value = &part[split_index + 1..]; 96 - 97 - if !key.is_empty() && !value.is_empty() { 98 - query_params.insert(key.to_string(), value.to_string()); 99 - } 100 - idx += 1; 101 - } else { 102 - break; 103 - } 104 - } 105 - 106 - // Next part should be the destination 107 - if idx >= parts.len() { 108 - return Ok(None); 109 - } 110 - 111 - let to = parts[idx]; 112 - idx += 1; 113 - 114 - // Parse remaining parts for status code 115 - for part in parts.iter().skip(idx) { 116 - // Check for status code (with optional ! for force) 117 - if let Some(stripped) = part.strip_suffix('!') { 118 - if let Ok(s) = stripped.parse::<u16>() { 119 - force = true; 120 - status = s; 121 - } 122 - } else if let Ok(s) = part.parse::<u16>() { 123 - status = s; 124 - } 125 - // Note: We're ignoring conditional redirects (Country, Language, Cookie, Role) for now 126 - // They can be added later if needed 127 - } 128 - 129 - // Parse the 'from' pattern 130 - let (pattern, params) = convert_path_to_regex(from)?; 131 - 132 - Ok(Some(RedirectRule { 133 - from: from.to_string(), 134 - to: to.to_string(), 135 - status, 136 - force, 137 - from_pattern: pattern, 138 - from_params: params, 139 - query_params: if query_params.is_empty() { 140 - None 141 - } else { 142 - Some(query_params) 143 - }, 144 - })) 145 - } 146 - 147 - /// Convert a path pattern with placeholders and splats to a regex 148 - /// Examples: 149 - /// /blog/:year/:month/:day -> captures year, month, day 150 - /// /news/* -> captures splat 151 - fn convert_path_to_regex(pattern: &str) -> Result<(Regex, Vec<String>), String> { 152 - let mut params = Vec::new(); 153 - let mut regex_str = String::from("^"); 154 - 155 - // Split by query string if present 156 - let path_part = pattern.split('?').next().unwrap_or(pattern); 157 - 158 - // Escape special regex characters except * and : 159 - let mut escaped = String::new(); 160 - for ch in path_part.chars() { 161 - match ch { 162 - '.' | '+' | '^' | '$' | '{' | '}' | '(' | ')' | '|' | '[' | ']' | '\\' => { 163 - escaped.push('\\'); 164 - escaped.push(ch); 165 - } 166 - _ => escaped.push(ch), 167 - } 168 - } 169 - 170 - // Replace :param with named capture groups 171 - let param_regex = Regex::new(r":([a-zA-Z_][a-zA-Z0-9_]*)").map_err(|e| e.to_string())?; 172 - let mut last_end = 0; 173 - let mut result = String::new(); 174 - 175 - for cap in param_regex.captures_iter(&escaped) { 176 - let m = cap.get(0).unwrap(); 177 - result.push_str(&escaped[last_end..m.start()]); 178 - result.push_str("([^/?]+)"); 179 - params.push(cap[1].to_string()); 180 - last_end = m.end(); 181 - } 182 - result.push_str(&escaped[last_end..]); 183 - escaped = result; 184 - 185 - // Replace * with splat capture 186 - if escaped.contains('*') { 187 - escaped = escaped.replace('*', "(.*)"); 188 - params.push("splat".to_string()); 189 - } 190 - 191 - regex_str.push_str(&escaped); 192 - 193 - // Make trailing slash optional 194 - if !regex_str.ends_with(".*") { 195 - regex_str.push_str("/?"); 196 - } 197 - 198 - regex_str.push('$'); 199 - 200 - let pattern = Regex::new(&regex_str).map_err(|e| e.to_string())?; 201 - 202 - Ok((pattern, params)) 203 - } 204 - 205 - /// Match a request path against redirect rules 206 - pub fn match_redirect_rule( 207 - request_path: &str, 208 - rules: &[RedirectRule], 209 - query_params: Option<&HashMap<String, String>>, 210 - ) -> Option<RedirectMatch> { 211 - // Normalize path: ensure leading slash 212 - let normalized_path = if request_path.starts_with('/') { 213 - request_path.to_string() 214 - } else { 215 - format!("/{}", request_path) 216 - }; 217 - 218 - for rule in rules { 219 - // Check query parameter conditions first (if any) 220 - if let Some(required_params) = &rule.query_params { 221 - if let Some(actual_params) = query_params { 222 - let query_matches = required_params.iter().all(|(key, expected_value)| { 223 - if let Some(actual_value) = actual_params.get(key) { 224 - // If expected value is a placeholder (:name), any value is acceptable 225 - if expected_value.starts_with(':') { 226 - return true; 227 - } 228 - // Otherwise it must match exactly 229 - actual_value == expected_value 230 - } else { 231 - false 232 - } 233 - }); 234 - 235 - if !query_matches { 236 - continue; 237 - } 238 - } else { 239 - // Rule requires query params but none provided 240 - continue; 241 - } 242 - } 243 - 244 - // Match the path pattern 245 - if let Some(captures) = rule.from_pattern.captures(&normalized_path) { 246 - let mut target_path = rule.to.clone(); 247 - 248 - // Replace captured parameters 249 - for (i, param_name) in rule.from_params.iter().enumerate() { 250 - if let Some(param_value) = captures.get(i + 1) { 251 - let value = param_value.as_str(); 252 - 253 - if param_name == "splat" { 254 - target_path = target_path.replace(":splat", value); 255 - } else { 256 - target_path = target_path.replace(&format!(":{}", param_name), value); 257 - } 258 - } 259 - } 260 - 261 - // Handle query parameter replacements 262 - if let Some(required_params) = &rule.query_params { 263 - if let Some(actual_params) = query_params { 264 - for (key, placeholder) in required_params { 265 - if placeholder.starts_with(':') { 266 - if let Some(actual_value) = actual_params.get(key) { 267 - let param_name = &placeholder[1..]; 268 - target_path = target_path.replace( 269 - &format!(":{}", param_name), 270 - actual_value, 271 - ); 272 - } 273 - } 274 - } 275 - } 276 - } 277 - 278 - // Preserve query string for 200, 301, 302 redirects (unless target already has one) 279 - if [200, 301, 302].contains(&rule.status) 280 - && query_params.is_some() 281 - && !target_path.contains('?') 282 - { 283 - if let Some(params) = query_params { 284 - if !params.is_empty() { 285 - let query_string: String = params 286 - .iter() 287 - .map(|(k, v)| format!("{}={}", k, v)) 288 - .collect::<Vec<_>>() 289 - .join("&"); 290 - target_path = format!("{}?{}", target_path, query_string); 291 - } 292 - } 293 - } 294 - 295 - return Some(RedirectMatch { 296 - target_path, 297 - status: rule.status, 298 - force: rule.force, 299 - }); 300 - } 301 - } 302 - 303 - None 304 - } 305 - 306 - /// Load redirect rules from a _redirects file 307 - pub fn load_redirect_rules(directory: &Path) -> Vec<RedirectRule> { 308 - let redirects_path = directory.join("_redirects"); 309 - 310 - if !redirects_path.exists() { 311 - return Vec::new(); 312 - } 313 - 314 - match fs::read_to_string(&redirects_path) { 315 - Ok(content) => parse_redirects_file(&content), 316 - Err(e) => { 317 - eprintln!("Failed to load _redirects file: {}", e); 318 - Vec::new() 319 - } 320 - } 321 - } 322 - 323 - #[cfg(test)] 324 - mod tests { 325 - use super::*; 326 - 327 - #[test] 328 - fn test_parse_simple_redirect() { 329 - let content = "/old-path /new-path"; 330 - let rules = parse_redirects_file(content); 331 - assert_eq!(rules.len(), 1); 332 - assert_eq!(rules[0].from, "/old-path"); 333 - assert_eq!(rules[0].to, "/new-path"); 334 - assert_eq!(rules[0].status, 301); 335 - assert!(!rules[0].force); 336 - } 337 - 338 - #[test] 339 - fn test_parse_with_status() { 340 - let content = "/temp /target 302"; 341 - let rules = parse_redirects_file(content); 342 - assert_eq!(rules[0].status, 302); 343 - } 344 - 345 - #[test] 346 - fn test_parse_force_redirect() { 347 - let content = "/force /target 301!"; 348 - let rules = parse_redirects_file(content); 349 - assert!(rules[0].force); 350 - } 351 - 352 - #[test] 353 - fn test_match_exact_path() { 354 - let rules = parse_redirects_file("/old-path /new-path"); 355 - let m = match_redirect_rule("/old-path", &rules, None); 356 - assert!(m.is_some()); 357 - assert_eq!(m.unwrap().target_path, "/new-path"); 358 - } 359 - 360 - #[test] 361 - fn test_match_splat() { 362 - let rules = parse_redirects_file("/news/* /blog/:splat"); 363 - let m = match_redirect_rule("/news/2024/01/15/post", &rules, None); 364 - assert!(m.is_some()); 365 - assert_eq!(m.unwrap().target_path, "/blog/2024/01/15/post"); 366 - } 367 - 368 - #[test] 369 - fn test_match_placeholders() { 370 - let rules = parse_redirects_file("/blog/:year/:month/:day /posts/:year-:month-:day"); 371 - let m = match_redirect_rule("/blog/2024/01/15", &rules, None); 372 - assert!(m.is_some()); 373 - assert_eq!(m.unwrap().target_path, "/posts/2024-01-15"); 374 - } 375 - }
-623
rust-cli/src/serve.rs
··· 1 - use crate::pull::pull_site; 2 - use crate::redirects::{load_redirect_rules, match_redirect_rule, RedirectRule}; 3 - use wisp_lexicons::place_wisp::settings::Settings; 4 - use axum::{ 5 - Router, 6 - extract::Request, 7 - response::{Response, IntoResponse, Redirect}, 8 - http::{StatusCode, Uri, header}, 9 - body::Body, 10 - }; 11 - use jacquard::CowStr; 12 - use jacquard::api::com_atproto::sync::subscribe_repos::{SubscribeRepos, SubscribeReposMessage}; 13 - use jacquard::api::com_atproto::repo::get_record::GetRecord; 14 - use jacquard_common::types::string::Did; 15 - use jacquard_common::xrpc::{SubscriptionClient, TungsteniteSubscriptionClient, XrpcExt}; 16 - use jacquard_common::IntoStatic; 17 - use jacquard_common::types::value::from_data; 18 - use miette::IntoDiagnostic; 19 - use n0_future::StreamExt; 20 - use std::collections::HashMap; 21 - use std::path::{PathBuf, Path}; 22 - use std::sync::Arc; 23 - use tokio::sync::RwLock; 24 - use tower::Service; 25 - use tower_http::compression::CompressionLayer; 26 - use tower_http::services::ServeDir; 27 - 28 - /// Shared state for the server 29 - #[derive(Clone)] 30 - struct ServerState { 31 - did: CowStr<'static>, 32 - rkey: CowStr<'static>, 33 - output_dir: PathBuf, 34 - last_cid: Arc<RwLock<Option<String>>>, 35 - redirect_rules: Arc<RwLock<Vec<RedirectRule>>>, 36 - settings: Arc<RwLock<Option<Settings<'static>>>>, 37 - } 38 - 39 - /// Fetch settings for a site from the PDS 40 - async fn fetch_settings( 41 - pds_url: &url::Url, 42 - did: &Did<'_>, 43 - rkey: &str, 44 - ) -> miette::Result<Option<Settings<'static>>> { 45 - use jacquard_common::types::ident::AtIdentifier; 46 - use jacquard_common::types::string::{Rkey as RkeyType, RecordKey}; 47 - 48 - let client = reqwest::Client::new(); 49 - let rkey_parsed = RkeyType::new(rkey).into_diagnostic()?; 50 - 51 - let request = GetRecord::new() 52 - .repo(AtIdentifier::Did(did.clone())) 53 - .collection(CowStr::from("place.wisp.settings")) 54 - .rkey(RecordKey::from(rkey_parsed)) 55 - .build(); 56 - 57 - match client.xrpc(pds_url.clone()).send(&request).await { 58 - Ok(response) => { 59 - let output = response.into_output().into_diagnostic()?; 60 - 61 - // Parse the record value as Settings 62 - match from_data::<Settings>(&output.value) { 63 - Ok(settings) => { 64 - Ok(Some(settings.into_static())) 65 - } 66 - Err(_) => { 67 - // Settings record exists but couldn't parse - use defaults 68 - Ok(None) 69 - } 70 - } 71 - } 72 - Err(_) => { 73 - // Settings record doesn't exist 74 - Ok(None) 75 - } 76 - } 77 - } 78 - 79 - /// Serve a site locally with real-time firehose updates 80 - pub async fn serve_site( 81 - input: CowStr<'static>, 82 - rkey: CowStr<'static>, 83 - output_dir: PathBuf, 84 - port: u16, 85 - ) -> miette::Result<()> { 86 - println!("Serving site {} from {} on port {}...", rkey, input, port); 87 - 88 - // Resolve handle to DID if needed 89 - use jacquard_identity::PublicResolver; 90 - use jacquard::prelude::IdentityResolver; 91 - 92 - let resolver = PublicResolver::default(); 93 - let did = if input.starts_with("did:") { 94 - Did::new(&input).into_diagnostic()? 95 - } else { 96 - // It's a handle, resolve it 97 - let handle = jacquard_common::types::string::Handle::new(&input).into_diagnostic()?; 98 - resolver.resolve_handle(&handle).await.into_diagnostic()? 99 - }; 100 - 101 - println!("Resolved to DID: {}", did.as_str()); 102 - 103 - // Resolve PDS URL (needed for settings fetch) 104 - let pds_url = resolver.pds_for_did(&did).await.into_diagnostic()?; 105 - 106 - // Create output directory if it doesn't exist 107 - std::fs::create_dir_all(&output_dir).into_diagnostic()?; 108 - 109 - // Initial pull of the site 110 - println!("Performing initial pull..."); 111 - let did_str = CowStr::from(did.as_str().to_string()); 112 - pull_site(did_str.clone(), rkey.clone(), output_dir.clone()).await?; 113 - 114 - // Fetch settings 115 - let settings = fetch_settings(&pds_url, &did, rkey.as_ref()).await?; 116 - if let Some(ref s) = settings { 117 - println!("\nSettings loaded:"); 118 - if let Some(true) = s.directory_listing { 119 - println!(" • Directory listing: enabled"); 120 - } 121 - if let Some(ref spa_file) = s.spa_mode { 122 - println!(" • SPA mode: enabled ({})", spa_file); 123 - } 124 - if let Some(ref custom404) = s.custom404 { 125 - println!(" • Custom 404: {}", custom404); 126 - } 127 - } else { 128 - println!("No settings configured (using defaults)"); 129 - } 130 - 131 - // Load redirect rules 132 - let redirect_rules = load_redirect_rules(&output_dir); 133 - if !redirect_rules.is_empty() { 134 - println!("Loaded {} redirect rules from _redirects", redirect_rules.len()); 135 - } 136 - 137 - // Create shared state 138 - let state = ServerState { 139 - did: did_str.clone(), 140 - rkey: rkey.clone(), 141 - output_dir: output_dir.clone(), 142 - last_cid: Arc::new(RwLock::new(None)), 143 - redirect_rules: Arc::new(RwLock::new(redirect_rules)), 144 - settings: Arc::new(RwLock::new(settings)), 145 - }; 146 - 147 - // Start firehose listener in background 148 - let firehose_state = state.clone(); 149 - tokio::spawn(async move { 150 - if let Err(e) = watch_firehose(firehose_state).await { 151 - eprintln!("Firehose error: {}", e); 152 - } 153 - }); 154 - 155 - // Create HTTP server with gzip compression and redirect handling 156 - let serve_dir = ServeDir::new(&output_dir).precompressed_gzip(); 157 - 158 - let app = Router::new() 159 - .fallback(move |req: Request| { 160 - let state = state.clone(); 161 - let mut serve_dir = serve_dir.clone(); 162 - async move { 163 - handle_request_with_redirects(req, state, &mut serve_dir).await 164 - } 165 - }) 166 - .layer(CompressionLayer::new()); 167 - 168 - let addr = format!("0.0.0.0:{}", port); 169 - let listener = tokio::net::TcpListener::bind(&addr) 170 - .await 171 - .into_diagnostic()?; 172 - 173 - println!("\n✓ Server running at http://localhost:{}", port); 174 - println!(" Watching for updates on the firehose...\n"); 175 - 176 - axum::serve(listener, app).await.into_diagnostic()?; 177 - 178 - Ok(()) 179 - } 180 - 181 - /// Serve a file for SPA mode 182 - async fn serve_file_for_spa(output_dir: &Path, spa_file: &str) -> Response { 183 - let file_path = output_dir.join(spa_file.trim_start_matches('/')); 184 - 185 - match tokio::fs::read(&file_path).await { 186 - Ok(contents) => { 187 - Response::builder() 188 - .status(StatusCode::OK) 189 - .header(header::CONTENT_TYPE, "text/html; charset=utf-8") 190 - .body(Body::from(contents)) 191 - .unwrap() 192 - } 193 - Err(_) => { 194 - StatusCode::NOT_FOUND.into_response() 195 - } 196 - } 197 - } 198 - 199 - /// Serve custom 404 page 200 - async fn serve_custom_404(output_dir: &Path, custom404_file: &str) -> Response { 201 - let file_path = output_dir.join(custom404_file.trim_start_matches('/')); 202 - 203 - match tokio::fs::read(&file_path).await { 204 - Ok(contents) => { 205 - Response::builder() 206 - .status(StatusCode::NOT_FOUND) 207 - .header(header::CONTENT_TYPE, "text/html; charset=utf-8") 208 - .body(Body::from(contents)) 209 - .unwrap() 210 - } 211 - Err(_) => { 212 - StatusCode::NOT_FOUND.into_response() 213 - } 214 - } 215 - } 216 - 217 - /// Serve directory listing 218 - async fn serve_directory_listing(dir_path: &Path, url_path: &str) -> Response { 219 - match tokio::fs::read_dir(dir_path).await { 220 - Ok(mut entries) => { 221 - let mut html = String::from("<!DOCTYPE html><html><head><meta charset='utf-8'><title>Directory listing</title>"); 222 - html.push_str("<style>body{font-family:sans-serif;margin:2em}a{display:block;padding:0.5em;text-decoration:none;color:#0066cc}a:hover{background:#f0f0f0}</style>"); 223 - html.push_str("</head><body>"); 224 - html.push_str(&format!("<h1>Index of {}</h1>", url_path)); 225 - html.push_str("<hr>"); 226 - 227 - // Add parent directory link if not at root 228 - if url_path != "/" { 229 - let parent = if url_path.ends_with('/') { 230 - format!("{}../", url_path) 231 - } else { 232 - format!("{}/", url_path.rsplitn(2, '/').nth(1).unwrap_or("/")) 233 - }; 234 - html.push_str(&format!("<a href='{}'>../</a>", parent)); 235 - } 236 - 237 - let mut items = Vec::new(); 238 - while let Ok(Some(entry)) = entries.next_entry().await { 239 - if let Ok(name) = entry.file_name().into_string() { 240 - let is_dir = entry.path().is_dir(); 241 - let display_name = if is_dir { 242 - format!("{}/", name) 243 - } else { 244 - name.clone() 245 - }; 246 - 247 - let link_path = if url_path.ends_with('/') { 248 - format!("{}{}", url_path, name) 249 - } else { 250 - format!("{}/{}", url_path, name) 251 - }; 252 - 253 - items.push((display_name, link_path, is_dir)); 254 - } 255 - } 256 - 257 - // Sort: directories first, then alphabetically 258 - items.sort_by(|a, b| { 259 - match (a.2, b.2) { 260 - (true, false) => std::cmp::Ordering::Less, 261 - (false, true) => std::cmp::Ordering::Greater, 262 - _ => a.0.cmp(&b.0), 263 - } 264 - }); 265 - 266 - for (display_name, link_path, _) in items { 267 - html.push_str(&format!("<a href='{}'>{}</a>", link_path, display_name)); 268 - } 269 - 270 - html.push_str("</body></html>"); 271 - 272 - Response::builder() 273 - .status(StatusCode::OK) 274 - .header(header::CONTENT_TYPE, "text/html; charset=utf-8") 275 - .body(Body::from(html)) 276 - .unwrap() 277 - } 278 - Err(_) => { 279 - StatusCode::NOT_FOUND.into_response() 280 - } 281 - } 282 - } 283 - 284 - /// Handle a request with redirect and settings support 285 - async fn handle_request_with_redirects( 286 - req: Request, 287 - state: ServerState, 288 - serve_dir: &mut ServeDir, 289 - ) -> Response { 290 - let uri = req.uri().clone(); 291 - let path = uri.path(); 292 - let method = req.method().clone(); 293 - 294 - // Parse query parameters 295 - let query_params = uri.query().map(|q| { 296 - let mut params = HashMap::new(); 297 - for pair in q.split('&') { 298 - if let Some((key, value)) = pair.split_once('=') { 299 - params.insert(key.to_string(), value.to_string()); 300 - } 301 - } 302 - params 303 - }); 304 - 305 - // Get settings 306 - let settings = state.settings.read().await.clone(); 307 - 308 - // Check for redirect rules first 309 - let redirect_rules = state.redirect_rules.read().await; 310 - if let Some(redirect_match) = match_redirect_rule(path, &redirect_rules, query_params.as_ref()) { 311 - let is_force = redirect_match.force; 312 - drop(redirect_rules); // Release the lock 313 - 314 - // If not forced, check if the file exists first 315 - if !is_force { 316 - // Try to serve the file normally first 317 - let test_req = Request::builder() 318 - .uri(uri.clone()) 319 - .method(&method) 320 - .body(axum::body::Body::empty()) 321 - .unwrap(); 322 - 323 - match serve_dir.call(test_req).await { 324 - Ok(response) if response.status().is_success() => { 325 - // File exists and was served successfully, return it 326 - return response.into_response(); 327 - } 328 - _ => { 329 - // File doesn't exist or error, apply redirect 330 - } 331 - } 332 - } 333 - 334 - // Handle different status codes 335 - match redirect_match.status { 336 - 200 => { 337 - // Rewrite: serve the target file but keep the URL the same 338 - if let Ok(target_uri) = redirect_match.target_path.parse::<Uri>() { 339 - let new_req = Request::builder() 340 - .uri(target_uri) 341 - .method(&method) 342 - .body(axum::body::Body::empty()) 343 - .unwrap(); 344 - 345 - match serve_dir.call(new_req).await { 346 - Ok(response) => response.into_response(), 347 - Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), 348 - } 349 - } else { 350 - StatusCode::INTERNAL_SERVER_ERROR.into_response() 351 - } 352 - } 353 - 301 => { 354 - // Permanent redirect 355 - Redirect::permanent(&redirect_match.target_path).into_response() 356 - } 357 - 302 => { 358 - // Temporary redirect 359 - Redirect::temporary(&redirect_match.target_path).into_response() 360 - } 361 - 404 => { 362 - // Custom 404 page 363 - if let Ok(target_uri) = redirect_match.target_path.parse::<Uri>() { 364 - let new_req = Request::builder() 365 - .uri(target_uri) 366 - .method(&method) 367 - .body(axum::body::Body::empty()) 368 - .unwrap(); 369 - 370 - match serve_dir.call(new_req).await { 371 - Ok(mut response) => { 372 - *response.status_mut() = StatusCode::NOT_FOUND; 373 - response.into_response() 374 - } 375 - Err(_) => StatusCode::NOT_FOUND.into_response(), 376 - } 377 - } else { 378 - StatusCode::NOT_FOUND.into_response() 379 - } 380 - } 381 - _ => { 382 - // Unsupported status code, fall through to normal serving 383 - match serve_dir.call(req).await { 384 - Ok(response) => response.into_response(), 385 - Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), 386 - } 387 - } 388 - } 389 - } else { 390 - drop(redirect_rules); 391 - 392 - // No redirect match, try to serve the file 393 - let response_result = serve_dir.call(req).await; 394 - 395 - match response_result { 396 - Ok(response) if response.status().is_success() => { 397 - // File served successfully 398 - response.into_response() 399 - } 400 - Ok(response) if response.status() == StatusCode::NOT_FOUND => { 401 - // File not found, check settings for fallback behavior 402 - if let Some(ref settings) = settings { 403 - // SPA mode takes precedence 404 - if let Some(ref spa_file) = settings.spa_mode { 405 - // Serve the SPA file for all non-file routes 406 - return serve_file_for_spa(&state.output_dir, spa_file.as_ref()).await; 407 - } 408 - 409 - // Check if path is a directory and directory listing is enabled 410 - if let Some(true) = settings.directory_listing { 411 - let file_path = state.output_dir.join(path.trim_start_matches('/')); 412 - if file_path.is_dir() { 413 - return serve_directory_listing(&file_path, path).await; 414 - } 415 - } 416 - 417 - // Check for custom 404 418 - if let Some(ref custom404) = settings.custom404 { 419 - return serve_custom_404(&state.output_dir, custom404.as_ref()).await; 420 - } 421 - } 422 - 423 - // No special handling, return 404 424 - StatusCode::NOT_FOUND.into_response() 425 - } 426 - Ok(response) => response.into_response(), 427 - Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), 428 - } 429 - } 430 - } 431 - 432 - /// Watch the firehose for updates to the specific site 433 - fn watch_firehose(state: ServerState) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<()>> + Send>> { 434 - Box::pin(async move { 435 - use jacquard_identity::PublicResolver; 436 - use jacquard::prelude::IdentityResolver; 437 - 438 - // Resolve DID to PDS URL 439 - let resolver = PublicResolver::default(); 440 - let did = Did::new(&state.did).into_diagnostic()?; 441 - let pds_url = resolver.pds_for_did(&did).await.into_diagnostic()?; 442 - 443 - println!("[PDS] Resolved DID to PDS: {}", pds_url); 444 - 445 - // Convert HTTP(S) URL to WebSocket URL 446 - let mut ws_url = pds_url.clone(); 447 - let scheme = if pds_url.scheme() == "https" { "wss" } else { "ws" }; 448 - ws_url.set_scheme(scheme) 449 - .map_err(|_| miette::miette!("Failed to set WebSocket scheme"))?; 450 - 451 - println!("[PDS] Connecting to {}...", ws_url); 452 - 453 - // Create subscription client 454 - let client = TungsteniteSubscriptionClient::from_base_uri(ws_url); 455 - 456 - // Subscribe to the PDS firehose 457 - let params = SubscribeRepos::new().build(); 458 - 459 - let stream = client.subscribe(&params).await.into_diagnostic()?; 460 - println!("[PDS] Connected! Watching for updates..."); 461 - 462 - // Convert to typed message stream 463 - let (_sink, mut messages) = stream.into_stream(); 464 - 465 - loop { 466 - match messages.next().await { 467 - Some(Ok(msg)) => { 468 - if let Err(e) = handle_firehose_message(&state, msg).await { 469 - eprintln!("[PDS] Error handling message: {}", e); 470 - } 471 - } 472 - Some(Err(e)) => { 473 - eprintln!("[PDS] Stream error: {}", e); 474 - // Try to reconnect after a delay 475 - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 476 - return Box::pin(watch_firehose(state)).await; 477 - } 478 - None => { 479 - println!("[PDS] Stream ended, reconnecting..."); 480 - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 481 - return Box::pin(watch_firehose(state)).await; 482 - } 483 - } 484 - } 485 - }) 486 - } 487 - 488 - /// Handle a firehose message 489 - async fn handle_firehose_message<'a>( 490 - state: &ServerState, 491 - msg: SubscribeReposMessage<'a>, 492 - ) -> miette::Result<()> { 493 - match msg { 494 - SubscribeReposMessage::Commit(commit_msg) => { 495 - // Check if this commit is from our DID 496 - if commit_msg.repo.as_str() != state.did.as_str() { 497 - return Ok(()); 498 - } 499 - 500 - // Check if any operation affects our site or settings 501 - let site_path = format!("place.wisp.fs/{}", state.rkey); 502 - let settings_path = format!("place.wisp.settings/{}", state.rkey); 503 - let has_site_update = commit_msg.ops.iter().any(|op| op.path.as_ref() == site_path); 504 - let has_settings_update = commit_msg.ops.iter().any(|op| op.path.as_ref() == settings_path); 505 - 506 - if has_site_update { 507 - // Debug: log all operations for this commit 508 - println!("[Debug] Commit has {} ops for {}", commit_msg.ops.len(), state.rkey); 509 - for op in &commit_msg.ops { 510 - if op.path.as_ref() == site_path { 511 - println!("[Debug] - {} {}", op.action.as_ref(), op.path.as_ref()); 512 - } 513 - } 514 - } 515 - 516 - if has_site_update { 517 - // Use the commit CID as the version tracker 518 - let commit_cid = commit_msg.commit.to_string(); 519 - 520 - // Check if this is a new commit 521 - let should_update = { 522 - let last_cid = state.last_cid.read().await; 523 - Some(commit_cid.clone()) != *last_cid 524 - }; 525 - 526 - if should_update { 527 - // Check operation types 528 - let has_create_or_update = commit_msg.ops.iter().any(|op| { 529 - op.path.as_ref() == site_path && 530 - (op.action.as_ref() == "create" || op.action.as_ref() == "update") 531 - }); 532 - let has_delete = commit_msg.ops.iter().any(|op| { 533 - op.path.as_ref() == site_path && op.action.as_ref() == "delete" 534 - }); 535 - 536 - // If there's a create/update, pull the site (even if there's also a delete in the same commit) 537 - if has_create_or_update { 538 - println!("\n[Update] Detected change to site {} (commit: {})", state.rkey, commit_cid); 539 - println!("[Update] Pulling latest version..."); 540 - 541 - // Pull the updated site 542 - match pull_site( 543 - state.did.clone(), 544 - state.rkey.clone(), 545 - state.output_dir.clone(), 546 - ) 547 - .await 548 - { 549 - Ok(_) => { 550 - // Update last CID 551 - let mut last_cid = state.last_cid.write().await; 552 - *last_cid = Some(commit_cid); 553 - 554 - // Reload redirect rules 555 - let new_redirect_rules = load_redirect_rules(&state.output_dir); 556 - let mut redirect_rules = state.redirect_rules.write().await; 557 - *redirect_rules = new_redirect_rules; 558 - 559 - println!("[Update] ✓ Site updated successfully!\n"); 560 - } 561 - Err(e) => { 562 - eprintln!("[Update] Failed to pull site: {}", e); 563 - } 564 - } 565 - } else if has_delete { 566 - // Only a delete, no create/update 567 - println!("\n[Update] Site {} was deleted", state.rkey); 568 - 569 - // Update last CID so we don't process this commit again 570 - let mut last_cid = state.last_cid.write().await; 571 - *last_cid = Some(commit_cid); 572 - } 573 - } 574 - } 575 - 576 - // Handle settings updates 577 - if has_settings_update { 578 - println!("\n[Settings] Detected change to settings"); 579 - 580 - // Resolve PDS URL 581 - use jacquard_identity::PublicResolver; 582 - use jacquard::prelude::IdentityResolver; 583 - 584 - let resolver = PublicResolver::default(); 585 - let did = Did::new(&state.did).into_diagnostic()?; 586 - let pds_url = resolver.pds_for_did(&did).await.into_diagnostic()?; 587 - 588 - // Fetch updated settings 589 - match fetch_settings(&pds_url, &did, state.rkey.as_ref()).await { 590 - Ok(new_settings) => { 591 - let mut settings = state.settings.write().await; 592 - *settings = new_settings.clone(); 593 - drop(settings); 594 - 595 - if let Some(ref s) = new_settings { 596 - println!("[Settings] Updated:"); 597 - if let Some(true) = s.directory_listing { 598 - println!(" • Directory listing: enabled"); 599 - } 600 - if let Some(ref spa_file) = s.spa_mode { 601 - println!(" • SPA mode: enabled ({})", spa_file); 602 - } 603 - if let Some(ref custom404) = s.custom404 { 604 - println!(" • Custom 404: {}", custom404); 605 - } 606 - } else { 607 - println!("[Settings] Cleared (using defaults)"); 608 - } 609 - } 610 - Err(e) => { 611 - eprintln!("[Settings] Failed to fetch updated settings: {}", e); 612 - } 613 - } 614 - } 615 - } 616 - _ => { 617 - // Ignore identity and account messages 618 - } 619 - } 620 - 621 - Ok(()) 622 - } 623 -
-497
rust-cli/src/subfs_utils.rs
··· 1 - use jacquard_common::types::string::AtUri; 2 - use jacquard_common::types::blob::BlobRef; 3 - use jacquard_common::IntoStatic; 4 - use jacquard::client::{Agent, AgentSession, AgentSessionExt}; 5 - use jacquard::prelude::IdentityResolver; 6 - use miette::IntoDiagnostic; 7 - use std::collections::HashMap; 8 - 9 - use wisp_lexicons::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode}; 10 - use wisp_lexicons::place_wisp::subfs::SubfsRecord; 11 - 12 - /// Extract all subfs URIs from a directory tree with their mount paths 13 - pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> { 14 - let mut uris = Vec::new(); 15 - 16 - for entry in &directory.entries { 17 - let full_path = if current_path.is_empty() { 18 - entry.name.to_string() 19 - } else { 20 - format!("{}/{}", current_path, entry.name) 21 - }; 22 - 23 - match &entry.node { 24 - FsEntryNode::Subfs(subfs_node) => { 25 - // Found a subfs node - store its URI and mount path 26 - uris.push((subfs_node.subject.to_string(), full_path.clone())); 27 - } 28 - FsEntryNode::Directory(subdir) => { 29 - // Recursively search subdirectories 30 - let sub_uris = extract_subfs_uris(subdir, full_path); 31 - uris.extend(sub_uris); 32 - } 33 - FsEntryNode::File(_) => { 34 - // Files don't contain subfs 35 - } 36 - FsEntryNode::Unknown(_) => { 37 - // Skip unknown nodes 38 - } 39 - } 40 - } 41 - 42 - uris 43 - } 44 - 45 - /// Fetch a subfs record from the PDS 46 - pub async fn fetch_subfs_record( 47 - agent: &Agent<impl AgentSession + IdentityResolver>, 48 - uri: &str, 49 - ) -> miette::Result<SubfsRecord<'static>> { 50 - // Parse URI: at://did/collection/rkey 51 - let parts: Vec<&str> = uri.trim_start_matches("at://").split('/').collect(); 52 - 53 - if parts.len() < 3 { 54 - return Err(miette::miette!("Invalid subfs URI: {}", uri)); 55 - } 56 - 57 - let _did = parts[0]; 58 - let collection = parts[1]; 59 - let _rkey = parts[2]; 60 - 61 - if collection != "place.wisp.subfs" { 62 - return Err(miette::miette!("Expected place.wisp.subfs collection, got: {}", collection)); 63 - } 64 - 65 - // Construct AT-URI for fetching 66 - let at_uri = AtUri::new(uri).into_diagnostic()?; 67 - 68 - // Fetch the record 69 - let response = agent.get_record::<SubfsRecord>(&at_uri).await.into_diagnostic()?; 70 - let record_output = response.into_output().into_diagnostic()?; 71 - 72 - Ok(record_output.value.into_static()) 73 - } 74 - 75 - /// Recursively fetch all subfs records (including nested ones) 76 - /// Returns a list of (mount_path, SubfsRecord) tuples 77 - /// Note: Multiple records can have the same mount_path (for flat-merged chunks) 78 - pub async fn fetch_all_subfs_records_recursive( 79 - agent: &Agent<impl AgentSession + IdentityResolver>, 80 - initial_uris: Vec<(String, String)>, 81 - ) -> miette::Result<Vec<(String, SubfsRecord<'static>)>> { 82 - use futures::stream::{self, StreamExt}; 83 - 84 - let mut all_subfs: Vec<(String, SubfsRecord<'static>)> = Vec::new(); 85 - let mut fetched_uris: std::collections::HashSet<String> = std::collections::HashSet::new(); 86 - let mut to_fetch = initial_uris; 87 - 88 - if to_fetch.is_empty() { 89 - return Ok(all_subfs); 90 - } 91 - 92 - println!("Found {} subfs records, fetching recursively...", to_fetch.len()); 93 - 94 - let mut iteration = 0; 95 - const MAX_ITERATIONS: usize = 10; 96 - 97 - while !to_fetch.is_empty() && iteration < MAX_ITERATIONS { 98 - iteration += 1; 99 - println!(" Iteration {}: fetching {} subfs records...", iteration, to_fetch.len()); 100 - 101 - let subfs_results: Vec<_> = stream::iter(to_fetch.clone()) 102 - .map(|(uri, mount_path)| async move { 103 - match fetch_subfs_record(agent, &uri).await { 104 - Ok(record) => Some((mount_path, record, uri)), 105 - Err(e) => { 106 - eprintln!(" ⚠️ Failed to fetch subfs {}: {}", uri, e); 107 - None 108 - } 109 - } 110 - }) 111 - .buffer_unordered(5) 112 - .collect() 113 - .await; 114 - 115 - // Process results and find nested subfs 116 - let mut newly_found_uris = Vec::new(); 117 - for result in subfs_results { 118 - if let Some((mount_path, record, uri)) = result { 119 - println!(" ✓ Fetched subfs at {}", mount_path); 120 - 121 - // Extract nested subfs URIs from this record 122 - let nested_uris = extract_subfs_uris_from_subfs_dir(&record.root, mount_path.clone()); 123 - newly_found_uris.extend(nested_uris); 124 - 125 - all_subfs.push((mount_path, record)); 126 - fetched_uris.insert(uri); 127 - } 128 - } 129 - 130 - // Filter out already-fetched URIs (based on URI, not path) 131 - to_fetch = newly_found_uris 132 - .into_iter() 133 - .filter(|(uri, _)| !fetched_uris.contains(uri)) 134 - .collect(); 135 - } 136 - 137 - if iteration >= MAX_ITERATIONS { 138 - eprintln!("⚠️ Max iterations reached while fetching nested subfs"); 139 - } 140 - 141 - println!(" Total subfs records fetched: {}", all_subfs.len()); 142 - 143 - Ok(all_subfs) 144 - } 145 - 146 - /// Extract subfs URIs from a subfs::Directory 147 - fn extract_subfs_uris_from_subfs_dir( 148 - directory: &wisp_lexicons::place_wisp::subfs::Directory, 149 - current_path: String, 150 - ) -> Vec<(String, String)> { 151 - let mut uris = Vec::new(); 152 - 153 - for entry in &directory.entries { 154 - match &entry.node { 155 - wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => { 156 - // Check if this is a chunk entry (chunk0, chunk1, etc.) 157 - // Chunks should be flat-merged, so use the parent's path 158 - let mount_path = if entry.name.starts_with("chunk") && 159 - entry.name.chars().skip(5).all(|c| c.is_ascii_digit()) { 160 - // This is a chunk - use parent's path for flat merge 161 - println!(" → Found chunk {} at {}, will flat-merge to {}", entry.name, current_path, current_path); 162 - current_path.clone() 163 - } else { 164 - // Normal subfs - append name to path 165 - if current_path.is_empty() { 166 - entry.name.to_string() 167 - } else { 168 - format!("{}/{}", current_path, entry.name) 169 - } 170 - }; 171 - 172 - uris.push((subfs_node.subject.to_string(), mount_path)); 173 - } 174 - wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => { 175 - let full_path = if current_path.is_empty() { 176 - entry.name.to_string() 177 - } else { 178 - format!("{}/{}", current_path, entry.name) 179 - }; 180 - let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path); 181 - uris.extend(nested); 182 - } 183 - _ => {} 184 - } 185 - } 186 - 187 - uris 188 - } 189 - 190 - /// Merge blob maps from subfs records into the main blob map (RECURSIVE) 191 - /// Returns the total number of blobs merged from all subfs records 192 - pub async fn merge_subfs_blob_maps( 193 - agent: &Agent<impl AgentSession + IdentityResolver>, 194 - subfs_uris: Vec<(String, String)>, 195 - main_blob_map: &mut HashMap<String, (BlobRef<'static>, String)>, 196 - ) -> miette::Result<usize> { 197 - // Fetch all subfs records recursively 198 - let all_subfs = fetch_all_subfs_records_recursive(agent, subfs_uris).await?; 199 - 200 - let mut total_merged = 0; 201 - 202 - // Extract blobs from all fetched subfs records 203 - // Skip parent records that only contain chunk references (no actual files) 204 - for (mount_path, subfs_record) in all_subfs { 205 - // Check if this record only contains chunk subfs references (no files) 206 - let only_has_chunks = subfs_record.root.entries.iter().all(|e| { 207 - matches!(&e.node, wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_)) && 208 - e.name.starts_with("chunk") && 209 - e.name.chars().skip(5).all(|c| c.is_ascii_digit()) 210 - }); 211 - 212 - if only_has_chunks && !subfs_record.root.entries.is_empty() { 213 - // This is a parent containing only chunks - skip it, blobs are in the chunks 214 - println!(" → Skipping parent subfs at {} ({} chunks, no files)", mount_path, subfs_record.root.entries.len()); 215 - continue; 216 - } 217 - 218 - let subfs_blob_map = extract_subfs_blobs(&subfs_record.root, mount_path.clone()); 219 - let count = subfs_blob_map.len(); 220 - 221 - for (path, blob_info) in subfs_blob_map { 222 - main_blob_map.insert(path, blob_info); 223 - } 224 - 225 - total_merged += count; 226 - println!(" ✓ Merged {} blobs from subfs at {}", count, mount_path); 227 - } 228 - 229 - Ok(total_merged) 230 - } 231 - 232 - /// Extract blobs from a subfs directory (works with subfs::Directory) 233 - /// Returns a map of file paths to their blob refs and CIDs 234 - fn extract_subfs_blobs( 235 - directory: &wisp_lexicons::place_wisp::subfs::Directory, 236 - current_path: String, 237 - ) -> HashMap<String, (BlobRef<'static>, String)> { 238 - let mut blob_map = HashMap::new(); 239 - 240 - for entry in &directory.entries { 241 - let full_path = if current_path.is_empty() { 242 - entry.name.to_string() 243 - } else { 244 - format!("{}/{}", current_path, entry.name) 245 - }; 246 - 247 - match &entry.node { 248 - wisp_lexicons::place_wisp::subfs::EntryNode::File(file_node) => { 249 - let blob_ref = &file_node.blob; 250 - let cid_string = blob_ref.blob().r#ref.to_string(); 251 - blob_map.insert( 252 - full_path, 253 - (blob_ref.clone().into_static(), cid_string) 254 - ); 255 - } 256 - wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => { 257 - let sub_map = extract_subfs_blobs(subdir, full_path); 258 - blob_map.extend(sub_map); 259 - } 260 - wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => { 261 - // Nested subfs - these should be resolved recursively in the main flow 262 - // For now, we skip them (they'll be fetched separately) 263 - eprintln!(" ⚠️ Found nested subfs at {}, skipping (should be fetched separately)", full_path); 264 - } 265 - wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(_) => { 266 - // Skip unknown nodes 267 - } 268 - } 269 - } 270 - 271 - blob_map 272 - } 273 - 274 - /// Count total files in a directory tree 275 - pub fn count_files_in_directory(directory: &FsDirectory) -> usize { 276 - let mut count = 0; 277 - 278 - for entry in &directory.entries { 279 - match &entry.node { 280 - FsEntryNode::File(_) => count += 1, 281 - FsEntryNode::Directory(subdir) => { 282 - count += count_files_in_directory(subdir); 283 - } 284 - FsEntryNode::Subfs(_) => { 285 - // Subfs nodes don't count towards the main manifest file count 286 - } 287 - FsEntryNode::Unknown(_) => {} 288 - } 289 - } 290 - 291 - count 292 - } 293 - 294 - /// Estimate JSON size of a directory tree 295 - pub fn estimate_directory_size(directory: &FsDirectory) -> usize { 296 - // Serialize to JSON and measure 297 - match serde_json::to_string(directory) { 298 - Ok(json) => json.len(), 299 - Err(_) => 0, 300 - } 301 - } 302 - 303 - /// Information about a directory that could be split into a subfs record 304 - #[derive(Debug)] 305 - pub struct SplittableDirectory { 306 - pub path: String, 307 - pub directory: FsDirectory<'static>, 308 - pub size: usize, 309 - pub file_count: usize, 310 - } 311 - 312 - /// Find large directories that could be split into subfs records 313 - /// Returns directories sorted by size (largest first) 314 - pub fn find_large_directories(directory: &FsDirectory, current_path: String) -> Vec<SplittableDirectory> { 315 - let mut result = Vec::new(); 316 - 317 - for entry in &directory.entries { 318 - if let FsEntryNode::Directory(subdir) = &entry.node { 319 - let dir_path = if current_path.is_empty() { 320 - entry.name.to_string() 321 - } else { 322 - format!("{}/{}", current_path, entry.name) 323 - }; 324 - 325 - let size = estimate_directory_size(subdir); 326 - let file_count = count_files_in_directory(subdir); 327 - 328 - result.push(SplittableDirectory { 329 - path: dir_path.clone(), 330 - directory: (*subdir.clone()).into_static(), 331 - size, 332 - file_count, 333 - }); 334 - 335 - // Recursively find subdirectories 336 - let subdirs = find_large_directories(subdir, dir_path); 337 - result.extend(subdirs); 338 - } 339 - } 340 - 341 - // Sort by size (largest first) 342 - result.sort_by(|a, b| b.size.cmp(&a.size)); 343 - 344 - result 345 - } 346 - 347 - /// Replace a directory with a subfs node in the tree 348 - pub fn replace_directory_with_subfs( 349 - directory: FsDirectory<'static>, 350 - target_path: &str, 351 - subfs_uri: &str, 352 - flat: bool, 353 - ) -> miette::Result<FsDirectory<'static>> { 354 - use jacquard_common::CowStr; 355 - use wisp_lexicons::place_wisp::fs::{Entry, Subfs}; 356 - 357 - let path_parts: Vec<&str> = target_path.split('/').collect(); 358 - 359 - if path_parts.is_empty() { 360 - return Err(miette::miette!("Cannot replace root directory")); 361 - } 362 - 363 - // Parse the subfs URI and make it owned/'static 364 - let at_uri = AtUri::new_cow(jacquard_common::CowStr::from(subfs_uri.to_string())).into_diagnostic()?; 365 - 366 - // If this is a root-level directory 367 - if path_parts.len() == 1 { 368 - let target_name = path_parts[0]; 369 - let new_entries: Vec<Entry> = directory.entries.into_iter().map(|entry| { 370 - if entry.name == target_name { 371 - // Replace this directory with a subfs node 372 - Entry::new() 373 - .name(entry.name) 374 - .node(FsEntryNode::Subfs(Box::new( 375 - Subfs::new() 376 - .r#type(CowStr::from("subfs")) 377 - .subject(at_uri.clone()) 378 - .flat(Some(flat)) 379 - .build() 380 - ))) 381 - .build() 382 - } else { 383 - entry 384 - } 385 - }).collect(); 386 - 387 - return Ok(FsDirectory::new() 388 - .r#type(CowStr::from("directory")) 389 - .entries(new_entries) 390 - .build()); 391 - } 392 - 393 - // Recursively navigate to parent directory 394 - let first_part = path_parts[0]; 395 - let remaining_path = path_parts[1..].join("/"); 396 - 397 - let new_entries: Vec<Entry> = directory.entries.into_iter().filter_map(|entry| { 398 - if entry.name == first_part { 399 - if let FsEntryNode::Directory(subdir) = entry.node { 400 - // Recursively process this subdirectory 401 - match replace_directory_with_subfs((*subdir).into_static(), &remaining_path, subfs_uri, flat) { 402 - Ok(updated_subdir) => { 403 - Some(Entry::new() 404 - .name(entry.name) 405 - .node(FsEntryNode::Directory(Box::new(updated_subdir))) 406 - .build()) 407 - } 408 - Err(_) => None, // Skip entries that fail to update 409 - } 410 - } else { 411 - Some(entry) 412 - } 413 - } else { 414 - Some(entry) 415 - } 416 - }).collect(); 417 - 418 - Ok(FsDirectory::new() 419 - .r#type(CowStr::from("directory")) 420 - .entries(new_entries) 421 - .build()) 422 - } 423 - 424 - /// Delete a subfs record from the PDS 425 - pub async fn delete_subfs_record( 426 - agent: &Agent<impl AgentSession + IdentityResolver>, 427 - uri: &str, 428 - ) -> miette::Result<()> { 429 - use jacquard_common::types::uri::RecordUri; 430 - 431 - // Construct AT-URI and convert to RecordUri 432 - let at_uri = AtUri::new(uri).into_diagnostic()?; 433 - let record_uri: RecordUri<'_, wisp_lexicons::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?; 434 - 435 - let rkey = record_uri.rkey() 436 - .ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))? 437 - .clone(); 438 - 439 - agent.delete_record::<SubfsRecord>(rkey).await.into_diagnostic()?; 440 - 441 - Ok(()) 442 - } 443 - 444 - /// Split a large directory into multiple smaller chunks 445 - /// Returns a list of chunk directories, each small enough to fit in a subfs record 446 - pub fn split_directory_into_chunks( 447 - directory: &FsDirectory, 448 - max_size: usize, 449 - ) -> Vec<FsDirectory<'static>> { 450 - use jacquard_common::CowStr; 451 - 452 - let mut chunks = Vec::new(); 453 - let mut current_chunk_entries = Vec::new(); 454 - let mut current_chunk_size = 100; // Base size for directory structure 455 - 456 - for entry in &directory.entries { 457 - // Estimate the size of this entry 458 - let entry_size = estimate_entry_size(entry); 459 - 460 - // If adding this entry would exceed the max size, start a new chunk 461 - if !current_chunk_entries.is_empty() && (current_chunk_size + entry_size > max_size) { 462 - // Create a chunk from current entries 463 - let chunk = FsDirectory::new() 464 - .r#type(CowStr::from("directory")) 465 - .entries(current_chunk_entries.clone()) 466 - .build(); 467 - 468 - chunks.push(chunk); 469 - 470 - // Start new chunk 471 - current_chunk_entries.clear(); 472 - current_chunk_size = 100; 473 - } 474 - 475 - current_chunk_entries.push(entry.clone().into_static()); 476 - current_chunk_size += entry_size; 477 - } 478 - 479 - // Add the last chunk if it has any entries 480 - if !current_chunk_entries.is_empty() { 481 - let chunk = FsDirectory::new() 482 - .r#type(CowStr::from("directory")) 483 - .entries(current_chunk_entries) 484 - .build(); 485 - chunks.push(chunk); 486 - } 487 - 488 - chunks 489 - } 490 - 491 - /// Estimate the JSON size of a single entry 492 - fn estimate_entry_size(entry: &wisp_lexicons::place_wisp::fs::Entry) -> usize { 493 - match serde_json::to_string(entry) { 494 - Ok(json) => json.len(), 495 - Err(_) => 500, // Conservative estimate if serialization fails 496 - } 497 - }