diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..00742101c --- /dev/null +++ b/.bazelignore @@ -0,0 +1,25 @@ +# Bazel output directories +bazel-out +bazel-bin +bazel-testlogs +bazel-genfiles + +# Bazel workspace symlinks +bazel-* + +# Bazel cache +.bazel_cache + +# User-specific bazelrc +.bazelrc.user + +# Generated files from crate_universe +crates_io_*.json + +# Go module files (managed by Bazel) +go.mod +go.sum + +# Node modules (managed by Bazel) +node_modules/ +package-lock.json \ No newline at end of file diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 000000000..c3e733b82 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,93 @@ +# Bazel configuration for Spider project + +# Build options +build --incompatible_enable_cc_toolchain_resolution + +# Fix for aws-lc-sys jitterentropy: allow crates to control their own optimization +build --repo_env=CRATE_CC_NO_DEFAULTS=1 + +# Common build settings +build --verbose_failures +build --worker_verbose + +# Output formatting +build --experimental_repository_cache_hardlinks + +# Feature flag configurations +# These can be enabled with --config= + +# Chrome browser automation +build:chrome --//:chrome=true + +# OpenAI integration +build:openai --//:openai=true + +# io_uring support (Linux only) +build:io_uring --//:io_uring=true + +# Gemini integration +build:gemini --//:gemini=true + +# WebDriver support +build:webdriver --//:webdriver=true + +# Serde serialization +build:serde --//:serde=true + +# Cache support +build:cache --//:cache=true + +# Sitemap support +build:sitemap --//:sitemap=true + +# Smart crawling +build:smart --//:smart=true + +# Cron scheduling +build:cron --//:cron=true + +# Tracing +build:tracing --//:tracing=true + +# Firewall +build:firewall --//:firewall=true + +# Spider Cloud integration +build:spider_cloud --//:spider_cloud=true + +# Agent features +build:agent --//:agent=true +build:agent_openai --//:agent_openai=true +build:agent_chrome --//:agent_chrome=true +build:agent_webdriver --//:agent_webdriver=true +build:agent_skills --//:agent_skills=true +build:agent_full --//:agent_full=true + +# Release build optimization +build:release --compilation_mode=opt +build:release --opt_level=3 +build:release --lto=full + +# Debug build +build:debug --compilation_mode=dbg + +# Test configuration +test --test_output=errors +test --test_tag_filters=-manual + +# Platform-specific settings +build:linux --platforms=@platforms//os:linux +build:macos --platforms=@platforms//os:macos +build:windows --platforms=@platforms//os:windows + +# Remote cache (optional - uncomment if using remote cache) +# build --remote_cache=grpc://your-cache-server:9092 + +# Try to use all available cores +build --jobs=auto + +# Disk cache (separate from output root) +build --disk_cache=~/.cache/bazel_cache + +# Documentation +build:docs --//:docs=true \ No newline at end of file diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..d82c0e493 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,22 @@ +# https://rustsec.org/advisories/ +[advisories] +ignore = [ + # lru 0.13.0 unsound IterMut — transitive via wreq + "RUSTSEC-2026-0002", + # rustls-pemfile unmaintained — transitive via warp (spider_worker) + "RUSTSEC-2025-0134", + # rsa timing sidechannel — transitive via sqlx-mysql (no fix available) + "RUSTSEC-2023-0071", + # backoff unmaintained — transitive via async-openai, memvid-rs + "RUSTSEC-2025-0012", + # bincode unmaintained — transitive via memvid-rs, http-cache + "RUSTSEC-2025-0141", + # fxhash unmaintained — transitive via selectors (spider_scraper) + "RUSTSEC-2025-0057", + # instant unmaintained — transitive via backoff + "RUSTSEC-2024-0384", + # number_prefix unmaintained — transitive via indicatif (memvid-rs) + "RUSTSEC-2025-0119", + # paste unmaintained — transitive dep + "RUSTSEC-2024-0436", +] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1424aea06 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{yml,yaml}] +indent_size = 2 + +[*.{md,markdown}] +trim_trailing_whitespace = false + +[*.toml] +indent_size = 4 + +[Makefile] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..bcc583be6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,60 @@ +name: Bug Report +description: Report a bug or unexpected behavior +labels: ["bug"] +body: + - type: textarea + id: description + attributes: + label: What happened? + description: A clear description of the bug. + placeholder: Describe the issue... + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: Minimal code or steps to reproduce the issue. + placeholder: | + 1. Create a Website with... + 2. Call crawl()... + 3. Observe... + render: rust + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen? + validations: + required: true + - type: input + id: version + attributes: + label: Spider version + placeholder: "2.45.25" + validations: + required: true + - type: dropdown + id: features + attributes: + label: Rendering mode + options: + - HTTP (default) + - Chrome (CDP) + - WebDriver + - Smart mode + - Not applicable + validations: + required: true + - type: input + id: rust-version + attributes: + label: Rust version + placeholder: "Output of `rustc --version`" + - type: input + id: os + attributes: + label: Operating system + placeholder: "e.g. macOS 15, Ubuntu 24.04, Windows 11" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..cd52dc3cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Discord + url: https://discord.spider.cloud + about: Ask questions and get help from the community + - name: Documentation + url: https://docs.rs/spider/latest/spider + about: API reference and crate documentation diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..e62d6790b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,24 @@ +name: Feature Request +description: Suggest a new feature or improvement +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + description: Describe the use case or pain point. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: How you'd like it to work. Include API examples if you have them. + render: rust + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you've thought about or workarounds you're using. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..236348a16 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +## What does this PR do? + + + +## How was this tested? + + + +## Checklist + +- [ ] `cargo test` passes +- [ ] `cargo fmt` applied +- [ ] New public APIs have doc comments +- [ ] Feature-gated behind a flag (if adding optional functionality) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..d4065d688 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + labels: + - "dependencies" diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 000000000..936de3c4b --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,19 @@ +name: Security Audit + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 6 * * 1" # Every Monday at 6am UTC + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cargo-audit + run: cargo install cargo-audit --locked || true + - name: Run audit + run: cargo audit diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 000000000..d1b852290 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,52 @@ +name: Benches +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + RUST_LOG: "off" + SPIDER_BENCH_URL: http://localhost:3000 + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + id: cache + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Setup Local Web server + id: server + run: | + set +e + git clone https://github.com/tailwindlabs/tailwindcss.com || exit 0 + cd tailwindcss.com || exit 0 + npm i --force || exit 0 + npm run build + if [ $? -eq 0 ]; then + echo "status=ok" >> $GITHUB_OUTPUT + npm run start & + else + echo "Server build failed" + echo "status=failed" >> $GITHUB_OUTPUT + fi + - name: Run Benchmark Compare OSS + if: steps.server.outputs.status == 'ok' + run: cargo bench --bench crawl + - name: Run Benchmark Compare OSS - Features[decentralized] + if: steps.server.outputs.status == 'ok' + run: cargo bench --bench crawl --features decentralized \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..c10f46d53 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,36 @@ +name: Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-lint-${{ hashFiles('**/Cargo.lock') }} + - name: Clippy (default features) + run: cargo clippy --workspace -- -D warnings + - name: Clippy (chrome) + run: cargo clippy -p spider --features chrome -- -D warnings + + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check formatting + run: cargo fmt --all -- --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..8d8868bb4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate release notes + id: notes + run: | + TAG="${GITHUB_REF#refs/tags/}" + PREV_TAG=$(git tag --sort=-v:refname | grep -v "$TAG" | head -n1) + if [ -n "$PREV_TAG" ]; then + echo "## Changes since $PREV_TAG" > notes.md + echo "" >> notes.md + git log "$PREV_TAG".."$TAG" --pretty=format:"- %s" --no-merges >> notes.md + else + echo "Initial release" > notes.md + fi + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + body_path: notes.md + generate_release_notes: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 000000000..47448598a --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,59 @@ +name: Rust +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPEN_ROUTER: ${{ secrets.OPEN_ROUTER }} + SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }} + RUN_LIVE_TESTS: ${{ secrets.RUN_LIVE_TESTS }} + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + id: cache + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Cargo check workspace + run: cargo check --workspace + - name: Cargo test spider_agent (default) + run: cargo test -p spider_agent + - name: Cargo test spider_agent (openai + search_serper) + run: cargo test -p spider_agent --features "openai search_serper" + - name: Cargo test spider_agent live smoke + if: env.RUN_LIVE_TESTS == '1' || env.RUN_LIVE_TESTS == 'true' || env.RUN_LIVE_TESTS == 'TRUE' + run: cargo test -p spider_agent --features "openai search_serper" --test live_env_smoke -- --nocapture + + io_uring: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-uring-${{ hashFiles('**/Cargo.lock') }} + - name: Cargo check spider with io_uring + run: cargo check -p spider --features io_uring + - name: Cargo test spider uring_fs + run: cargo test -p spider --features io_uring uring_fs + - name: Cargo test spider (no io_uring fallback) + run: cargo test -p spider --no-default-features --features basic uring_fs diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..14e6ac5a8 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days." + stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days." + close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity." + close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity." + days-before-issue-stale: 30 + days-before-pr-stale: 45 + days-before-issue-close: 5 + days-before-pr-close: 10 diff --git a/.gitignore b/.gitignore index eccd7b4ab..9f245a019 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,15 @@ -/target/ +target **/*.rs.bk +.env +.DS_Store +_temp_spider_downloads +storage +http-cacache +release.sh +spider_transformations/example.pdf +spider_fingerprint/notes.txt +result +result-* +example.png +.idea +scripts/ diff --git a/BAZEL_FILES.md b/BAZEL_FILES.md new file mode 100644 index 000000000..ebfa8696f --- /dev/null +++ b/BAZEL_FILES.md @@ -0,0 +1,242 @@ +# Bazel 迁移新增文件说明 + +本文档详细说明了从 Cargo 构建系统迁移到 Bazel 构建系统时新增的所有文件及其作用。 + +--- + +## 核心配置文件 + +| 文件 | 作用 | +|------|------| +| **`MODULE.bazel`** | Bazel 模块定义(Bzlmod 模式),声明项目名称、版本、依赖(rules_rust、platforms 等) | +| **`MODULE.bazel.lock`** | 锁定依赖精确版本,确保可重复构建(类似 Cargo.lock) | +| **`WORKSPACE.bzlmod`** | 工作区配置,设置 Rust 工具链、crate_universe(Cargo 依赖转换)、Go 工具链等 | +| **`BUILD.bazel`** (根目录) | 定义 Feature Flags(40+ 个开关),如 `chrome`、`openai`、`io_uring` 等 | + +--- + +## 辅助配置文件 + +| 文件 | 作用 | +|------|------| +| **`crates.bzl`** | crate_universe 配置,将 `Cargo.lock` 中的依赖转换为 Bazel 目标 `@crates_io//:xxx` | +| **`features.bzl`** | Feature Flag 文档,记录所有特性的默认值、依赖关系、rustc flags | +| **`.bazelrc`** | Bazel 运行配置,定义 `--config=chrome`、`--config=release` 等快捷方式 | +| **`.bazelignore`** | 忽略文件,排除 Cargo 构建产物等 | + +### 各文件详解 + +#### `MODULE.bazel` + +Bazel 6.0+ 引入的新模块系统(Bzlmod),替代传统的 WORKSPACE 依赖管理方式: + +```starlark +module( + name = "spider", + version = "2.47.22", +) + +# 声明依赖 +bazel_dep(name = "rules_rust", version = "0.63.0") +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "bazel_skylib", version = "1.8.2") + +# Rust 工具链配置 +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain(edition = "2021", versions = ["1.94.0"]) + +# 从 Cargo.lock 导入依赖 +crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate.from_cargo(name = "crates_io", cargo_lockfile = "//:Cargo.lock", ...) +``` + +#### `WORKSPACE.bzlmod` + +传统工作区配置文件,在 Bzlmod 模式下作为补充: + +- 加载 `rules_rust` 和 Rust 工具链 +- 配置 `crate_universe` 从 Cargo.lock 生成依赖 +- 预留 Go 工具链配置(已注释) +- 平台定义和 Skylib 工具库 + +#### `crates.bzl` + +定义 `crates_repository`,将 Cargo 依赖映射到 Bazel: + +```starlark +crates_repository( + name = "crates_io", + cargo_lockfile = "//:Cargo.lock", + manifests = [ + "//:Cargo.toml", + "//spider:Cargo.toml", + // ... 其他 Cargo.toml + ], + supported_targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-apple-darwin", + // ... 其他平台 + ], +) +``` + +#### `features.bzl` + +集中管理所有 Feature Flags 的定义: + +```starlark +FEATURE_FLAGS = { + "chrome": { + "default": False, + "description": "Enable Chrome browser automation", + "rustc_flag": 'feature="chrome"', + "deps": ["chromey", "base64", "rand", ...], + "implies": ["serde", "cookies"], + }, + "openai": { ... }, + "io_uring": { ... }, + // ... +} +``` + +#### `.bazelrc` + +Bazel 命令行配置,提供快捷方式: + +```bash +# 启用 Chrome 特性 +bazel build //spider:spider --config=chrome + +# 发布构建 +bazel build //spider_cli:spider --config=release + +# 平台特定构建 +bazel build //spider:spider --config=linux +``` + +--- + +## 各模块构建文件 + +| 文件 | 作用 | +|------|------| +| **`spider/BUILD.bazel`** | 核心爬虫库构建规则 | +| **`spider_cli/BUILD.bazel`** | CLI 工具构建规则 | +| **`spider_worker/BUILD.bazel`** | Worker 服务构建规则 | +| **`spider_agent/BUILD.bazel`** | Agent 库构建规则 | +| **`spider_agent_types/BUILD.bazel`** | Agent 类型定义构建规则 | +| **`spider_agent_html/BUILD.bazel`** | HTML 处理库构建规则 | +| **`spider_utils/BUILD.bazel`** | 工具库构建规则 | +| **`benches/BUILD.bazel`** | 基准测试(支持 Rust + Go) | +| **`examples/BUILD.bazel`** | 示例代码构建 | + +### 构建文件示例 + +```starlark +# spider_cli/BUILD.bazel +rust_binary( + name = "spider", + srcs = glob(["src/**/*.rs"]), + crate_root = "src/main.rs", + deps = [ + "//spider:spider", + "@crates_io//:clap", + "@crates_io//:tokio", + # ... + ], + rustc_flags = select({ + "//:chrome_enabled": ["--cfg", 'feature="chrome"'], + "//conditions:default": [], + }), +) +``` + +--- + +## 文档文件 + +| 文件 | 作用 | +|------|------| +| **`BAZEL_README.md`** | Bazel 使用指南(中文),包含构建命令、Feature Flags 说明、常见问题 | +| **`BAZEL_MIGRATION.md`** | 迁移复盘记录,记录迁移过程、遇到的问题和解决方案 | + +--- + +## 辅助脚本与文件 + +| 文件 | 作用 | +|------|------| +| **`patch_module.sh`** | 模块补丁脚本 | +| **`all_crates.txt`** | 所有 crate 列表 | + +--- + +## Bazel 输出目录(应在 .gitignore 中排除) + +| 目录 | 作用 | +|------|------| +| **`bazel-bin`** | 编译输出(符号链接) | +| **`bazel-out`** | 实际构建产物 | +| **`bazel-spider`** | 工作区符号链接 | +| **`bazel-testlogs`** | 测试日志 | + +--- + +## 为什么迁移到 Bazel? + +1. **多语言支持** - 统一构建 Rust + Go + Node.js +2. **增量构建** - 沙盒和缓存机制实现秒级增量编译 +3. **可重复构建** - 严格的依赖隔离,确保构建结果一致 +4. **企业级特性** - 支持分布式缓存、远程执行 + +--- + +## Cargo 与 Bazel 共存 + +两套构建系统可以同时使用: + +```bash +# Cargo 方式 +cargo build --release + +# Bazel 方式 +bazel build //spider_cli:spider --config=release +``` + +依赖管理: +- **Cargo**: `Cargo.toml` 和 `Cargo.lock` 仍然是依赖声明的源 +- **Bazel**: `crate_universe` 从 `Cargo.lock` 生成 Bazel 依赖 + +更新依赖流程: +```bash +# 1. 更新 Cargo.toml +cargo add + +# 2. 更新 Cargo.lock +cargo update + +# 3. Bazel 会自动使用新的依赖 +bazel build //spider:spider +``` + +--- + +## 常用命令对照 + +| 操作 | Cargo | Bazel | +|------|-------|-------| +| 构建库 | `cargo build -p spider` | `bazel build //spider:spider` | +| 构建 CLI | `cargo build -p spider_cli` | `bazel build //spider_cli:spider` | +| 发布构建 | `cargo build --release` | `bazel build //... --config=release` | +| 启用特性 | `cargo build --features chrome` | `bazel build //... --config=chrome` | +| 运行测试 | `cargo test` | `bazel test //...` | +| 清理 | `cargo clean` | `bazel clean` | +| 查看依赖 | `cargo tree` | `bazel query "deps(//spider:spider)"` | + +--- + +## 相关链接 + +- [Bazel 官方文档](https://bazel.build/) +- [rules_rust](https://github.com/bazelbuild/rules_rust) +- [Bzlmod 迁移指南](https://bazel.build/external/migration) \ No newline at end of file diff --git a/BAZEL_MIGRATION.md b/BAZEL_MIGRATION.md new file mode 100644 index 000000000..2bf02112e --- /dev/null +++ b/BAZEL_MIGRATION.md @@ -0,0 +1,66 @@ +# Spider 项目 Bazel 迁移汇总与复盘记录 + +本文档主要记录了将 `spider` 原有的 Cargo 构建系统平滑且完全地迁移(或并行支持)到 **Bazel** 构建系统的全过程。通过一系列调试和修复,目前全工作区(`bazel build //...`)已经达到了 **100% 构建成功 (Exit Code 0)** 的状态。 + +--- + +## 1. 迁移方法论 (Methodology) + +本次迁移采用的核心策略如下: +1. **基础设施依赖**:借助 `@rules_rust` 管理 Rust 编译规则,并通过 `bzlmod` (`MODULE.bazel`) 结合 `cargo_universe` 完成对外部第三方依赖的解析,将其映射为 `@crates_io//:` 的扁平形式。 +2. **渐进式迭代验证**:采用了“构建 -> 报错 -> 解析日志 -> 修复依赖链 -> 重试”的闭环策略。通过反复执行 `bazel build //...`,让 Rustc 编译器主动暴露 `BUILD.bazel` 与 `Cargo.toml` 中信息不对等的地方。 +3. **严格声明式映射**:在 Cargo 中很多隐式行为(例如模块的自动发现、特性的隐式继承),在 Bazel 中都需要转变为**显式声明**(Explicit Declaration)。 + +--- + +## 2. 所做的核心改变 (Key Changes Made) + +整个项目按组件维度进行了大量的构建规则修复: + +### 2.1 `spider` (核心库) +* **添加缺失的系统与底层依赖**:在 `spider/BUILD.bazel` 中,为底层新增了 `"@crates_io//:libc"`、`"@crates_io//:flexbuffers"`。 +* **特性标志 (Feature Flags) 同步**:补全了 `spider` 库编译所必须的 `--cfg feature="..."` 标志(如 `flexbuffers`),使得下游依赖此特性的 `spider_worker` 能够被成功提供 API。 + +### 2.2 `spider_cli` (命令行套件) +* **由单文件转为全源码扫描**:原本 `BUILD.bazel` 中仅声明了 `srcs = ["src/main.rs"]`。由于 CLI 引用了内部的 `cli.rs` 和 `args.rs` 等模块,导致了 `E0583 (file not found for module)` 报错。 +* **修复方法**:引入 Bazel 的 Glob 机制,将其修改为 `srcs = glob(["src/**/*.rs"])`,确保所有的源码文件一并喂给 `rustc` 解析。 + +### 2.3 `spider_agent` & `spider_utils` (工具与代理层) +* **修复 Proc Macro 声明错位**:在 `spider_agent/BUILD.bazel` 中,原本将宏库 `"@crates_io//:async-trait"` 放到了 `deps` 里,在 Bazel 中会导致它被当做普通依赖来构建。我们将其移到了**专用的 `proc_macro_deps`** 列表中。 +* **Python 数组语法及幽灵依赖**:清理了 `spider_utils` 中破损的 Bazel List 语法,并剔除了未被 Cargo Universe 正确导出的可选依赖 `indexmap`,消除了 Unresolved 的解析报错。 + +### 2.4 `examples` & `benches` (示例与基准测试) +* **海量二进制目标的精确导向**:示例目录下有几十个单一的可执行文件(如 `scrape`, `serde_example` 等)。针对构建中出现的 `E0432`, `E0463` 等依赖缺失错误,分别为对应的 target 手动注入了 `"@crates_io//:env_logger"`, `"@crates_io//:flexbuffers"` 以及局部依赖 `"//spider_utils:spider_utils"`。 +* **多模块测试修复**:在基准测试 `benches/BUILD.bazel` 中,`crawl.rs` 包含外部模块 `go_colly` 等,原配置无法读取到。通过 `srcs = glob(["*.rs"])` 加上明确指向入口文件 `crate_root = "crawl.rs"` 完美化解。 + +--- + +## 3. 面临的挑战与 Debug 总结 (Debugging & Troubleshooting) + +由于 Bazel 提倡的是**气系隔离(Hermetic Build)**和**严格边界**,通常在 Cargo 自动包含的功能,在 Bazel 下会频繁报错。遇到并解决的典型问题包括: + +### 问题 A:未解析的模块 / 包 (Error `E0432`, `E0433`, `E0463`) +* **症状**:`can't find crate for 'xxx'` 或 `unresolved import`。 +* **根因**:开发者在 Cargo.toml 声明了依赖,却忘了在 `BUILD.bazel` 中同步写入到对应目标的 `deps` 中。由于 Bazel 的沙盒机制(Linux Sandbox),未在 `deps` 声明的依赖**绝对无法访问**。 +* **解决**:找到抛错的文件,定位其属于哪个 Bazel target,然后在 `deps` 中精确加上 `@crates_io//:xxx` 或对应的 `//folder:target`。 + +### 问题 B:多文件模块的 `srcs` 加载失败 (`E0583`) +* **症状**:报 `file not found for module 'xxx'`,哪怕文件实际上就在同一目录下。 +* **根因**:`Cargo` 会从 `main.rs` 出发扫描依赖图并自动调取硬盘文件;而 `Bazel` 必须要在 `srcs` 显式喂给它所有要参与编译的文件列表。如果只写 `["main.rs"]`,其它文件不会被挂载入编译沙盒。 +* **解决**:常规的包全量采用 `srcs = glob(["src/**/*.rs"])`,确保一切 Rust 源代码均可见。当有多个主入口并在同一个目录时(如 benches),要配合 `crate_root = "xxx.rs"` 指定入口点。 + +### 问题 C:特性开关 (Features) 没有打通 +* **症状**:某项方法“不存在”,如 `spider_worker` 报错无法引用 `spider::flexbuffers`。 +* **根因**:对于具备众多 feature 开关的库(比如本作 `spider`),虽然被依赖,但若被依赖侧的 rule 未传入对应的 `rustc_flags = ["--cfg", "feature=\"...\""]`,那条件编译 `#[cfg(feature = "...")]` 里面的代码将会被无情剔除。 +* **解决**:在底层 `rust_library` (比如 spider 自身) 和上层调用方都配置对应的 feature `select` 宏和 flags。 + +--- + +## 4. 结论与里程碑 +历经上述修复调优后,现在整个包含 **Cargo + bzlmod** 的拓扑双重体系已经被我们对接完毕。 +以后凡是依赖、源文件发生变更,只需保持上述对应原则。目前可实现一键秒级解析和增量缓存编译: +```bash +bazel build //... +bazel run //spider_cli:spider -- --help +``` +顺利迈入企业级的增量构建时代! \ No newline at end of file diff --git a/BAZEL_README.md b/BAZEL_README.md new file mode 100644 index 000000000..aa46254f0 --- /dev/null +++ b/BAZEL_README.md @@ -0,0 +1,361 @@ +# Spider Bazel 构建指南 + +本项目已从 Cargo 构建系统迁移到 Bazel,支持多语言构建(Rust + Go + Node.js)。 + +## 目录 + +- [环境要求](#环境要求) +- [快速开始](#快速开始) +- [项目结构](#项目结构) +- [构建命令](#构建命令) +- [Feature Flags](#feature-flags) +- [多语言支持](#多语言支持) +- [与 Cargo 共存](#与-cargo-共存) +- [文件说明](#文件说明) + +## 环境要求 + +- Bazel 7.0+ 或 Bazelisk +- Rust 工具链(stable) +- Go 工具链(可选,用于 Go 基准测试) +- Node.js(可选,用于 Node.js 基准测试) + +## 快速开始 + +```bash +# 构建核心库 +bazel build //spider:spider + +# 构建命令行工具 +bazel build //spider_cli:spider + +# 构建 Worker 服务 +bazel build //spider_worker:spider_worker + +# 构建所有目标 +bazel build //... +``` + +## 项目结构 + +``` +spider/ +├── WORKSPACE # Bazel 工作区配置 +├── BUILD.bazel # 根构建文件(Feature Flags 定义) +├── .bazelrc # Bazel 配置 +├── .bazelignore # Bazel 忽略文件 +├── crates.bzl # crate_universe 依赖配置 +├── features.bzl # Feature Flag 文档和辅助函数 +│ +├── spider/ # 核心库 - 爬虫引擎 +│ └── BUILD.bazel +├── spider_cli/ # CLI 工具 +│ └── BUILD.bazel +├── spider_worker/ # Worker/Proxy 服务 +│ └── BUILD.bazel +├── spider_utils/ # 工具库 +│ └── BUILD.bazel +├── spider_agent/ # Agent 库 +│ └── BUILD.bazel +├── spider_agent_types/ # Agent 类型定义 +│ └── BUILD.bazel +├── spider_agent_html/ # HTML 处理 +│ └── BUILD.bazel +├── benches/ # 基准测试 +│ └── BUILD.bazel +└── examples/ # 示例代码 + └── BUILD.bazel +``` + +### Crate 依赖关系 + +``` +spider_agent_types ←────────────────────────────────┐ + ↓ │ +spider_agent_html ──→ spider_agent ──→ spider ──────┘ + ↓ ↓ + spider_cli spider_worker +``` + +## 构建命令 + +### 基础构建 + +```bash +# 构建核心库 +bazel build //spider:spider + +# 构建命令行工具 +bazel build //spider_cli:spider + +# 构建 Worker 服务 +bazel build //spider_worker:spider_worker + +# 构建所有库 +bazel build //spider/... //spider_agent/... //spider_utils/... +``` + +### 启用特性 + +```bash +# 启用 Chrome 浏览器自动化 +bazel build //spider:spider --config=chrome + +# 启用 OpenAI 集成 +bazel build //spider:spider --config=openai + +# 启用 Gemini 集成 +bazel build //spider:spider --config=gemini + +# 启用 WebDriver 支持 +bazel build //spider:spider --config=webdriver + +# 启用缓存 +bazel build //spider:spider --config=cache + +# 启用 Smart 模式 +bazel build //spider:spider --config=smart + +# 组合多个特性 +bazel build //spider:spider --config=chrome --config=openai +``` + +### 发布构建 + +```bash +# 优化构建(相当于 cargo build --release) +bazel build //spider_cli:spider --config=release + +# 完全 LTO 优化 +bazel build //spider_cli:spider --config=release --features=full_lto +``` + +### 平台特定构建 + +```bash +# Linux 特定 +bazel build //spider:spider --config=linux + +# macOS 特定 +bazel build //spider:spider --config=macos + +# Windows 特定 +bazel build //spider:spider --config=windows +``` + +## Feature Flags + +本项目支持 40+ 个 Feature Flags,以下是主要特性: + +### 核心特性 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `sync` | true | 启用同步爬取 API | +| `serde` | true | 启用序列化支持 | +| `io_uring` | true | 启用 io_uring(仅 Linux) | +| `encoding` | false | 启用字符编码检测 | +| `time` | false | 启用时间跟踪 | +| `cookies` | false | 启用 Cookie 支持 | + +### 浏览器自动化 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `chrome` | false | 启用 Chrome 浏览器自动化 | +| `chrome_headed` | false | 有头模式运行 Chrome | +| `chrome_stealth` | false | Chrome 隐身模式 | +| `chrome_screenshot` | false | Chrome 截图功能 | +| `chrome_intercept` | false | Chrome 请求拦截 | +| `smart` | false | 智能爬取模式 | +| `webdriver` | false | WebDriver 支持 | + +### LLM 集成 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `openai` | false | OpenAI 集成 | +| `gemini` | false | Gemini 集成 | + +### 缓存 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `cache` | false | HTTP 请求缓存 | +| `cache_mem` | false | 内存缓存 | +| `cache_openai` | false | OpenAI 响应缓存 | + +### Agent 特性 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `agent` | false | Agent 集成 | +| `agent_openai` | false | Agent + OpenAI | +| `agent_chrome` | false | Agent + Chrome | +| `agent_webdriver` | false | Agent + WebDriver | +| `agent_skills` | false | Agent 技能 | +| `agent_full` | false | 完整 Agent 功能集 | + +### 其他特性 + +| Flag | 默认值 | 说明 | +|------|--------|------| +| `sitemap` | false | Sitemap 解析 | +| `cron` | false | 定时任务 | +| `tracing` | false | Tracing 支持 | +| `firewall` | false | 防火墙检测 | +| `spider_cloud` | false | Spider Cloud 集成 | + +## 多语言支持 + +### Go 基准测试 + +```bash +# 构建 Go 版本的爬虫基准测试 +bazel build //benches:gospider + +# 运行 Go 基准测试 +./bazel-bin/benches/gospider https://example.com +``` + +### Rust 基准测试 + +```bash +# 构建爬取基准测试 +bazel build //benches:crawl_bench + +# 构建核心基准测试 +bazel build //benches:core_bench +``` + +### Node.js 支持 + +Node.js 支持已预留,需要配置 `rules_nodejs` 或 `rules_js`。取消 `benches/BUILD.bazel` 中的注释即可启用。 + +## 与 Cargo 共存 + +Bazel 构建系统与 Cargo 可以共存,支持渐进式迁移: + +```bash +# 使用 Cargo 构建 +cargo build + +# 使用 Bazel 构建 +bazel build //... + +# 两者可以同时使用 +cargo build && bazel build //spider_cli:spider +``` + +### 依赖管理 + +- **Cargo**: `Cargo.toml` 和 `Cargo.lock` 仍然是依赖声明的源 +- **Bazel**: `crate_universe` 从 `Cargo.lock` 生成 Bazel 依赖 + +当更新依赖时: + +```bash +# 1. 更新 Cargo.toml +cargo add + +# 2. 更新 Cargo.lock +cargo update + +# 3. Bazel 会自动使用新的依赖 +bazel build //spider:spider +``` + +## 文件说明 + +### WORKSPACE + +工作区配置文件,包含: +- `rules_rust` - Rust 规则 +- `crate_universe` - Cargo 依赖转换 +- `rules_go` - Go 规则 +- `bazel_gazelle` - Go BUILD 文件生成 +- `platforms` - 平台定义 +- `bazel_skylib` - 通用工具 + +### BUILD.bazel + +各目录的构建文件,定义: +- `rust_library` - Rust 库目标 +- `rust_binary` - Rust 二进制目标 +- `go_binary` - Go 二进制目标 +- `select()` - 条件编译 + +### crates.bzl + +crate_universe 配置,从 Cargo 依赖生成 Bazel 目标。 + +### features.bzl + +Feature Flag 文档,包含: +- 所有特性的说明 +- 依赖关系 +- 辅助函数 + +### .bazelrc + +Bazel 配置文件,包含: +- 构建选项 +- Feature Flag 映射 +- 发布构建配置 + +## 常见问题 + +### Q: 如何查看可用的构建目标? + +```bash +bazel query //... +``` + +### Q: 如何查看特定目标的依赖? + +```bash +bazel query "deps(//spider:spider)" +``` + +### Q: 如何清理构建缓存? + +```bash +bazel clean +``` + +### Q: 如何查看构建详情? + +```bash +bazel build //spider:spider --verbose_failures +``` + +### Q: io_uring 只在 Linux 上可用,如何禁用? + +```bash +bazel build //spider:spider --//spider:io_uring=false +``` + +## 验证步骤 + +```bash +# 1. 基础构建验证 +bazel build //spider:spider +bazel build //spider_cli:spider +bazel build //spider_worker:spider_worker + +# 2. Feature flags 验证 +bazel build //spider:spider --config=chrome +bazel build //spider:spider --config=openai + +# 3. 发布构建验证 +bazel build //spider_cli:spider --config=release + +# 4. 与 Cargo 输出对比 +cargo build --release +bazel build //spider_cli:spider --config=release +``` + +## 联系方式 + +- 项目地址: https://github.com/spider-rs/spider +- 问题反馈: https://github.com/spider-rs/spider/issues \ No newline at end of file diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000..36712652f --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,340 @@ +""" +Root BUILD file for Spider project. +Exports workspace-level files and defines feature flags. +""" + +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +package(default_visibility = ["//visibility:public"]) + +# Export workspace files +exports_files([ + "Cargo.toml", + "Cargo.lock", + "LICENSE", + "README.md", +]) + +############################################################################### +# Feature Flags +# These control conditional compilation and dependencies +############################################################################### + +# Core feature flags +bool_flag( + name = "chrome", + build_setting_default = False, +) + +bool_flag( + name = "openai", + build_setting_default = False, +) + +bool_flag( + name = "gemini", + build_setting_default = False, +) + +bool_flag( + name = "io_uring", + build_setting_default = True, +) + +bool_flag( + name = "serde", + build_setting_default = True, +) + +bool_flag( + name = "cache", + build_setting_default = False, +) + +bool_flag( + name = "cache_mem", + build_setting_default = False, +) + +bool_flag( + name = "sitemap", + build_setting_default = False, +) + +bool_flag( + name = "smart", + build_setting_default = False, +) + +bool_flag( + name = "cron", + build_setting_default = False, +) + +bool_flag( + name = "tracing", + build_setting_default = False, +) + +bool_flag( + name = "firewall", + build_setting_default = False, +) + +bool_flag( + name = "webdriver", + build_setting_default = False, +) + +bool_flag( + name = "spider_cloud", + build_setting_default = False, +) + +bool_flag( + name = "wreq", + build_setting_default = False, +) + +bool_flag( + name = "full_resources", + build_setting_default = False, +) + +bool_flag( + name = "socks", + build_setting_default = False, +) + +bool_flag( + name = "encoding", + build_setting_default = False, +) + +bool_flag( + name = "headers", + build_setting_default = False, +) + +bool_flag( + name = "time", + build_setting_default = False, +) + +bool_flag( + name = "sync", + build_setting_default = True, +) + +bool_flag( + name = "cookies", + build_setting_default = False, +) + +bool_flag( + name = "glob", + build_setting_default = False, +) + +# Agent feature flags +bool_flag( + name = "agent", + build_setting_default = False, +) + +bool_flag( + name = "agent_openai", + build_setting_default = False, +) + +bool_flag( + name = "agent_chrome", + build_setting_default = False, +) + +bool_flag( + name = "agent_webdriver", + build_setting_default = False, +) + +bool_flag( + name = "agent_skills", + build_setting_default = False, +) + +bool_flag( + name = "agent_full", + build_setting_default = False, +) + +bool_flag( + name = "docs", + build_setting_default = False, +) + +############################################################################### +# Config Settings for Feature Flags +# These are used in select() statements +# Note: config_setting is a native Bazel rule, not loaded from skylib +############################################################################### + +config_setting( + name = "chrome_enabled", + flag_values = {":chrome": "true"}, +) + +config_setting( + name = "openai_enabled", + flag_values = {":openai": "true"}, +) + +config_setting( + name = "gemini_enabled", + flag_values = {":gemini": "true"}, +) + +config_setting( + name = "io_uring_enabled", + flag_values = {":io_uring": "true"}, +) + +config_setting( + name = "serde_enabled", + flag_values = {":serde": "true"}, +) + +config_setting( + name = "cache_enabled", + flag_values = {":cache": "true"}, +) + +config_setting( + name = "cache_mem_enabled", + flag_values = {":cache_mem": "true"}, +) + +config_setting( + name = "sitemap_enabled", + flag_values = {":sitemap": "true"}, +) + +config_setting( + name = "smart_enabled", + flag_values = {":smart": "true"}, +) + +config_setting( + name = "cron_enabled", + flag_values = {":cron": "true"}, +) + +config_setting( + name = "tracing_enabled", + flag_values = {":tracing": "true"}, +) + +config_setting( + name = "firewall_enabled", + flag_values = {":firewall": "true"}, +) + +config_setting( + name = "webdriver_enabled", + flag_values = {":webdriver": "true"}, +) + +config_setting( + name = "spider_cloud_enabled", + flag_values = {":spider_cloud": "true"}, +) + +config_setting( + name = "wreq_enabled", + flag_values = {":wreq": "true"}, +) + +config_setting( + name = "agent_enabled", + flag_values = {":agent": "true"}, +) + +config_setting( + name = "agent_openai_enabled", + flag_values = {":agent_openai": "true"}, +) + +config_setting( + name = "agent_chrome_enabled", + flag_values = {":agent_chrome": "true"}, +) + +config_setting( + name = "agent_webdriver_enabled", + flag_values = {":agent_webdriver": "true"}, +) + +config_setting( + name = "agent_skills_enabled", + flag_values = {":agent_skills": "true"}, +) + +config_setting( + name = "agent_full_enabled", + flag_values = {":agent_full": "true"}, +) + +config_setting( + name = "encoding_enabled", + flag_values = {":encoding": "true"}, +) + +config_setting( + name = "headers_enabled", + flag_values = {":headers": "true"}, +) + +config_setting( + name = "time_enabled", + flag_values = {":time": "true"}, +) + +config_setting( + name = "sync_enabled", + flag_values = {":sync": "true"}, +) + +config_setting( + name = "cookies_enabled", + flag_values = {":cookies": "true"}, +) + +config_setting( + name = "glob_enabled", + flag_values = {":glob": "true"}, +) + +config_setting( + name = "full_resources_enabled", + flag_values = {":full_resources": "true"}, +) + +config_setting( + name = "socks_enabled", + flag_values = {":socks": "true"}, +) + +# Platform config settings +config_setting( + name = "linux", + constraint_values = ["@platforms//os:linux"], +) + +config_setting( + name = "macos", + constraint_values = ["@platforms//os:macos"], +) + +config_setting( + name = "windows", + constraint_values = ["@platforms//os:windows"], +) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..99852e30b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,519 @@ +# CHANGELOG + +## Unreleased + +1. chore: fix clippy warnings and formatting across workspace +1. chore: remove cargo dependabot for Rust (too noisy), keep github-actions only +1. chore(deps): bump flexbuffers 2 -> 25, async-openai 0.32 -> 0.33 +1. docs: rewrite README, add issue/PR templates, security policy, CI workflows +1. docs: add Contributor Covenant v2.1 Code of Conduct +1. docs: rewrite CONTRIBUTING.md and add zero-config quick start to README + +## v2.45.22 + +1. fix(cache): proper HTTP staleness for Chrome-cached pages +1. fix(cache): Period policy bypasses HTTP `is_stale` for Chrome-rendered pages + +## v2.45.21 + +1. fix(cache): enable `cache_chrome_hybrid_mem` feature for Chrome cache writes + +## v2.45.19 + +1. fix(chrome): default `no_sandbox()` for headless BrowserConfig (#354) + +## v2.45.18 + +1. feat(agent): per-round model pool routing for cost-optimized automation +1. test: comprehensive crawler-test.com integration suite (302 tests, 408 URLs) + +## v2.45.17 + +1. perf(agent): skip model scoring for pools with 2 or fewer models + +## v2.45.16 + +1. test(agent): comprehensive multi-LLM router reliability tests + +## v2.45.15 + +1. feat(cli): add `--wait-for` capabilities to spider_cli (#352) + +## v2.45.14 + +1. fix: Chrome mode honors `wait_for` config for networkIdle before HTML extraction + +## v2.45.13 + +1. fix: smart mode lifecycle waiting -- match Chrome coverage without re-fetch + +## v2.45.12 + +1. fix: auto-retry www. URLs on SSL protocol errors -- strip www prefix + +## v2.45.11 + +1. fix: reject empty HTML from all cache and seeded resource paths + +## v2.45.10 + +1. fix: invalidate empty HTML shell responses on cache read path + +## v2.45.9 + +1. feat: deferred Chrome -- cache-only crawl phase before browser launch + +## v2.45.8 + +1. feat: cache-first fast path -- skip browser/HTTP when cache has data + +## v2.45.7 + +1. fix: add `Website::with_hedge()` forwarding method + +## v2.45.5 + +1. feat: add work-stealing (hedged requests) for slow crawl requests +1. fix: skip HTML-specific heuristics in cache empty check for non-HTML content +1. fix: skip caching empty/near-empty HTML responses + +## v2.45.3 + +1. fix(uring_fs): fix compile errors when io_uring feature is enabled + +## v2.45.2 + +1. perf(io_uring): add StreamingWriter for streaming file I/O + +## v2.45.1 + +1. feat(io_uring): expand io_uring integration for file I/O +1. refactor(agent): extract types and HTML cleaning into `spider_agent_types` and `spider_agent_html` crates + +## v2.45.0 + +1. chore(deps): bump hashbrown 0.15 → 0.16 +1. chore(deps): bump strum 0.26 → 0.27 +1. chore(deps): bump thiserror 1 → 2 +1. chore(deps): bump indexmap 1 → 2 +1. chore(deps): bump async-openai 0.29 → 0.32 +1. chore(deps): bump tiktoken-rs 0.7 → 0.9 +1. chore(deps): bump sysinfo 0.35 → 0.38 +1. chore(deps): bump quick-xml 0.38 → 0.39 +1. chore(deps): bump criterion 0.5 → 0.8 +1. fix(openai): update async-openai imports for v0.32 types::chat module +1. fix(page): use resolver().resolve_element() for quick-xml 0.39 + +## v2.44.42 + +1. feat(agent): integrate llm_models_spider v0.1.9 with smart model selection +1. perf(website): use `take()` instead of `clone()` for subdomain base URL + +## v2.44.41 + +1. fix(website): use page's own URL for relative link resolution on subdomains (#351) + +## v2.44.40 + +1. docs: add all missing feature flags (`spider_cloud`, `agent`, `search`, `webdriver`, `wreq`, `adblock`, `simd`, `tracing`, etc.) +1. docs: add Spider Cloud and Chrome rendering integration examples + +## v2.44.39 + +1. refactor(agent): move all skill content to `spider_skills` crate (110 skills via `include_str!`) + +## v2.44.38 + +1. feat(agent): add L15-L48 not-a-robot skills (34 new levels) +1. feat(agent): add NEST engine loop for recursive nested challenges +1. feat(agent): add CIRCLE engine loop for drawing challenges +1. feat(agent): add haiku benchmark for agent evaluation + +## v2.44.37 + +1. fix: add `should_use_chrome_ai` and `use_chrome_ai` to stub `RemoteMultimodalConfigs` (chrome-only builds) + +## v2.44.36 + +1. feat(agent): add L8-L14 not-a-robot skills (license plate, nested, whack-a-mole, waldo, chihuahuas, reverse, affirmations) +1. feat(agent): add WAM engine loop for whack-a-mole challenges +1. feat(agent): Chrome AI element probe improvements + +## v2.44.33 + +1. feat(cache): optimize automation caching for skip-browser flows + +## v2.44.30 + +1. feat: add spider cloud end-to-end examples + +## v2.44.29 + +1. feat(agent): improve remote multimodal automation reliability + +## v2.44.28 + +1. feat(agent): expose optional automation reasoning in metadata + +## v2.44.26 + +1. feat(spider_cli): add runtime `--http` and `--headless` mode controls + +## v2.44.25 + +1. feat(agent): dual-model routing with per-endpoint URL and API key configuration +1. feat(agent): extraction-only mode optimization for single-round data extraction + +## v2.44.21 + +1. fix: feature flag compilation across `wreq`, `agent_full`, and `cache` combos +1. fix: `agent_full` memvid `!Send` compat via `spawn_blocking` +1. fix: `cache_chrome_hybrid` GEMINI_CLIENT lazy_static cfg gate +1. fix: `detect_cf_turnstyle` cfg gate (chrome, not real_browser) + +## v2.44.20 + +1. fix: broken `chrome` feature — missing `relevant` field and cfg gate on `detect_cf_turnstyle` (#349) + +## v2.44.18 + +1. feat(agent): add URL-level relevance pre-filter for crawling — classify URLs via text model before fetching, skip irrelevant ones +1. feat(agent): add `url_prefilter` and `relevance_gate` configuration + +## v2.44.17 + +1. perf: trie `entry_ref` optimization (-11% lookup time) +1. perf: robot parser hoisted `to_lowercase` (-13% parse time) +1. perf: `prepare_url` byte indexing optimization + +## v2.44.16 + +1. feat(agent): add relevance gate for remote multimodal — LLM returns `relevant: true|false`, irrelevant pages get wildcard budget refunded + +## v2.44.15 + +1. perf: trie 49-70% faster via optimized hot paths +1. perf: robot parser 50% faster +1. perf: HTML cleaner selector merging +1. chore: add criterion benchmarks for trie, robot parser, and URL preparation + +## v2.44.13 + +1. feat(spider): add `spider_cloud` integration and S3 skills loading + +## v2.44.12 + +1. feat(spider_agent): add dual-model routing (vision + text model selection) +1. feat(spider_agent): add long-term experience memory for automation sessions + +## v2.44.10 + +1. feat(spider_agent): improve skill triggers and board reading for web challenges + +## v2.44.9 + +1. feat(spider_agent): add Claude-optimized automation features +1. feat(agent): add `pre_evaluate` field on skills — engine runs JS before LLM inference + +## v2.44.8 + +1. feat(spider_agent): add concurrent page spawning with `OpenPage` action + +## v2.44.7 + +1. feat(spider_agent): integrate `llm_models_spider` for auto-updated vision model detection + +## v2.44.6 + +1. feat(spider): enable HTTP extraction without `agent_chrome` feature + +## v2.44.5 + +1. feat(agent): enhance CAPTCHA handling and lock system prompt +1. feat(agent): Chrome AI (Gemini Nano) integration — on-device LLM via `LanguageModel` API + +## v2.44.3 + +1. feat(agent): consolidate automation into `spider_agent` with seamless feature integration +1. feat: granular `AutomationUsage` tracking (tokens, api_calls, screenshots) +1. feat(spider_agent): add usage limits, custom tools, and granular tracking + +## v2.43.22 + +1. feat(automation): add `api_calls` tracking to AutomationUsage +1. feat(page): make `remote_multimodal_usage` and `extra_remote_multimodal_data` work for HTTP-only crawls (not just Chrome) +1. feat(page): add `usage` field to AutomationResults for per-result token tracking + +## spider_agent v0.5.1 + +1. feat(automation): add `api_calls` tracking to AutomationUsage for counting LLM API calls + +## v2.43.21 + +1. chore(spider): update spider_agent dependency to 0.5 + +## spider_agent v0.5.0 + +1. feat(actions): add complete WebAutomation parity with 17 new ActionType variants + - Click variants: ClickAll, ClickPoint, ClickHold, ClickHoldPoint, ClickDrag, ClickDragPoint, ClickAllClickable + - Wait variants: WaitFor, WaitForWithTimeout, WaitForNavigation, WaitForDom, WaitForAndClick + - Scroll variants: ScrollX, ScrollY, InfiniteScroll + - Input: Fill (clear + type) + - Chain control: ValidateChain +1. feat(automation): add PromptUrlGate for URL-based prompt/config overrides + - Exact URL matching + - Path-prefix matching (case-insensitive) + - Per-URL config overrides +1. feat(browser): add comprehensive browser methods for all new action types + - click_all, click_point, click_hold, click_hold_point + - click_drag, click_drag_point, click_all_clickable + - wait_for_timeout, wait_for_navigation, wait_for_dom, wait_and_click + - scroll_x, scroll_y, infinite_scroll + - fill, find_elements, get_element_bounds +1. feat(config): add system_prompt, system_prompt_extra, user_message_extra to AutomationConfig + +## v2.43.20 + +1. fix(spider): fix doctest and update chromey for adblock compatibility +1. fix(search): use reqwest::Client directly for cache feature compatibility +1. chore(spider): update spider_agent dependency to 0.4 + +## spider_agent v0.4.0 + +1. feat(cache): add SmartCache with size-aware LRU eviction and TTL expiration +1. feat(executor): add ChainExecutor for parallel step execution with response caching +1. feat(executor): add BatchExecutor for efficient batch processing +1. feat(executor): add PrefetchManager for predictive page loading +1. feat(router): add ModelRouter for smart model selection based on task complexity +1. feat(llm): add MessageContent helper methods (as_text, full_text, is_text, has_images) +1. fix(config): default ModelPolicy now allows High tier routing + +## spider_agent v0.3.0 + +1. feat(automation): add comprehensive automation module with action chains +1. feat(automation): add self-healing selector cache with LRU eviction +1. feat(automation): add content analysis for smart screenshot decisions +1. feat(automation): add configurable model policies and retry strategies + +## spider_agent v0.2.0 + +1. feat(memory): enhance memory with URL, action, and extraction history +1. feat(webdriver): add webdriver support via thirtyfour +1. feat(browser): add chrome browser and temp storage support + +## v2 + +### Multimodal AI Integration + +1. feat(openai): OpenAI integration for dynamic browser scripting and automation +1. feat(gemini): Gemini AI support for intelligent web interaction +1. feat(solver): built-in Gemini Nano support for web challenge solving +1. feat(chrome): remote multimodal web automation with vision capabilities +1. feat(automation): token usage tracking for LLM-powered extraction + +### Agentic Web Automation + +1. feat(automation): simplified agentic APIs - `act()`, `observe()`, `extract()` +1. feat(automation): agentic memory for multi-round automation sessions +1. feat(automation): prompt-based website configuration +1. feat(automation): selector cache with self-healing and LRU eviction +1. feat(automation): structured outputs with ExtractionSchema +1. feat(automation): autonomous agent with action chaining and error recovery +1. feat(automation): intelligent screenshot detection based on content analysis +1. feat(automation): byte-size-based smart HTML cleaning for optimal performance +1. feat(llm_json): robust JSON parsing from LLM outputs with thinking model support + +### WebDriver Support + +1. feat(webdriver): WebDriver support via thirtyfour crate +1. feat(webdriver): Selenium Grid and remote browser connectivity +1. feat(webdriver): multi-browser support (Chrome, Firefox, Edge) +1. feat(webdriver): stealth mode with spider_fingerprint integration +1. feat(webdriver): automation script support +1. feat(webdriver): screenshot capabilities + +### Web Search Integration + +1. feat(search): web search integration with multiple providers +1. feat(search): Serper.dev, Brave Search, Bing, and Tavily AI Search support +1. feat(search): `search_and_extract()` for combined search + data extraction +1. feat(search): `research()` method for multi-source topic research + +### spider_agent Crate + +1. feat(agent): standalone concurrent-safe multimodal agent crate +1. feat(agent): feature-gated LLM providers (OpenAI, OpenAI-compatible) +1. feat(agent): feature-gated search providers (Serper, Brave, Bing, Tavily) +1. feat(agent): Chrome browser automation support +1. feat(agent): smart caching with LRU eviction and TTL expiration +1. feat(agent): high-performance chain executor with parallel step support +1. feat(agent): batch processing and prefetch management +1. feat(agent): smart model routing based on task complexity + +### Browser & Chrome Enhancements + +1. feat(chrome): remote cache support (disk and memory) +1. feat(chrome): skip browser mode with smart HTML cleaning +1. feat(chrome): adblock integration via chromey +1. feat(chrome): idle network detection for page load completion +1. feat(chrome): auto geo-detection +1. feat(chrome): max page bytes control +1. feat(smart): improved smart mode with JS rendering detection +1. feat(smart): Imperva and sessionStorage detection handling + +### Anti-Bot & Security + +1. feat(antibot): anti-bot detection capabilities +1. feat(fingerprint): centralized browser fingerprint emulation +1. feat(fingerprint): header emulation for stealth +1. feat(solver): deterministic and AI-powered web challenge solvers +1. feat(solver): Lemin solver support +1. feat(firewall): firewall integration for request filtering + +### Data Processing + +1. feat(transform): HTML transformation crate with spider_transformations +1. feat(css_scraping): CSS/XPath scraping with spider_utils +1. feat(page): metadata extraction from pages +1. feat(website): seeded page link and metadata extraction +1. feat(decentralized): improved decentralized crawling with remote multimodal support + +### Performance & Infrastructure + +1. feat(cache): hybrid caching (Chrome + HTTP cache) +1. feat(cache): memory and disk cache options +1. feat(cmd): command-line crawling support +1. feat(disk): shared state multi-profiling +1. perf(website): reduced unnecessary clones and allocations +1. chore(chrome): stabilized concurrent screenshot handling + +## v1.98.0 + +1. feat(whitelist): whitelist routes to only crawl. + +## v1.85.0 + +1. feat(openai): use OpenAI to dynamically drive the browser. + +## v1.84.1 + +1. feat(chrome): add chrome_headless_new flag + +## v1.83.11 + +1. chore(chrome): add wait_for events + +## v1.60.0 + +1. feat(smart): add smart mode feature flag (HTTP until JS Rendering is needed per page) + +## v1.50.1 + +1. feat(cron): add cron feature flag [#153] + +## v1.36.0 + +1. feat(sync): subscribe to page updates to perform async handling of data + +## v1.31.0 + +1. feat(js): add init of script parsing + +## v1.30.5 + +1. feat(worker): add tls support + +## v1.30.3 + +1. chore(request): add custom domain redirect policy + +## v1.30.2 + +1. chore(glob): fix glob crawl establish + +## v1.30.1 + +1. chore(crawl): fix crawl asset detection and trailing start + +## v1.29.0 + +1. feat(fs): add temp storage resource handling (#112) +1. feat(url-glob): URL globbing (#113) thanks to [@roniemartinez](https://github.com/roniemartinez)) + +## v1.28.5 + +1. chore(request): fix resource success handling + +## v1.28.0 + +1. feat(proxies): add proxy support + +## v1.27.2 + +1. feat(decentralization): add workload split + +## v1.19.36 + +1. perf(crawl): add join handle task management + +## v1.19.26 + +1. perf(links): add fast pre serialized url anchor link extracting and reduced memory usage +1. perf(links): fix case sensitivity handling +1. perf(crawl): reduce memory usage on link gathering +1. chore(crawl): remove `Website.reset` method and improve crawl handling resource usage ( `reset` not needed now ) +1. chore(crawl): add heap usage of links visited +1. perf(crawl): massive scans capability to utilize more cpu +1. feat(timeout): add optional `configuration.request_timeout` duration +1. build(tokio): remove unused `net` feature +1. chore(docs): add missing scrape section + +## v1.10.7 + +- perf(req): enable brotli +- chore(tls): add ALPN tls defaults +- chore(statics): add initial static media ignore +- chore(robots): add shared client handling across parsers +- feat(crawl): add subdomain and tld crawling + +## v1.6.1 + +- perf(links): filter dup links after async batch +- chore(delay): fix crawl delay thread groups +- perf(page): slim channel page sending required props + +## v1.5.3 + +- feat(regex): add optional regex black listing + +## v1.5.0 + +- chore(bin): fix bin executable [#17](https://github.com/madeindjs/spider/pull/17/commits/b41e25fc507c6cd3ef251d2e25c97b936865e1a9) +- feat(cli): add cli separation binary [#17](https://github.com/madeindjs/spider/pull/17/commits/b41e25fc507c6cd3ef251d2e25c97b936865e1a9) +- feat(robots): add robots crawl delay respect and ua assign [#24](https://github.com/madeindjs/spider/pull/24) +- feat(async): add async page body gathering +- perf(latency): add connection re-use across request [#25](https://github.com/madeindjs/spider/pull/25) + +## v1.4.0 + +- feat(cli): add cli ability ([#16](https://github.com/madeindjs/spider/pull/16) thanks to [@j-mendez](https://github.com/j-mendez)) +- feat(concurrency): dynamic concurrent cpu defaults ([#15](https://github.com/madeindjs/spider/pull/15) thanks to [@j-mendez](https://github.com/j-mendez)) +- docs: add a changelog + +## v1.3.1 + +- fix(crawl): fix field type ([#14](https://github.com/madeindjs/spider/pull/14) thanks to [@j-mendez](https://github.com/j-mendez)) + +## v1.3.0 + +- feat(crawl): callback to run when link is found ([#13](https://github.com/madeindjs/spider/pull/13) thanks to [@j-mendez](https://github.com/j-mendez)) + +## v1.2.0 + +- Add User Agent configuration ([#5](https://github.com/madeindjs/spider/pull/5) thanks to [@Dragnucs](https://github.com/Dragnucs)) +- Add polite delay ([#6](https://github.com/madeindjs/spider/pull/6) thanks to [@Dragnucs](https://github.com/Dragnucs) ) + +## v1.1.3 + +- Handle page get errors ([#4](https://github.com/madeindjs/spider/pull/4) thanks to [@Dragnucs](https://github.com/Dragnucs)) +- Fix link resolution ([#3](https://github.com/madeindjs/spider/pull/3) thanks to [@Dragnucs](https://github.com/Dragnucs)) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..30373a8f5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Commitment + +We as members, contributors, and leaders are committed to making participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We are dedicated to acting and interacting in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@spider.cloud. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..d65c2e48d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,124 @@ +# Contributing to Spider + +Thanks for taking the time to contribute! Spider is an open-source web crawler and scraper built in Rust, and contributions of all kinds are welcome — bug fixes, new features, documentation improvements, and performance optimizations. + +## Getting Started + +### Prerequisites + +- [**Rust**](https://rustup.rs/) (edition 2021, stable toolchain) +- **Git** +- For Chrome/CDP features: a local Chrome or Chromium installation + +### Setup + +```bash +git clone https://github.com/spider-rs/spider.git +cd spider +cargo build +``` + +Run the test suite: + +```bash +cargo test +``` + +For Chrome-specific tests: + +```bash +cargo test --features chrome +``` + +### Project Structure + +``` +spider/ Core crawling library (Website, Page, Configuration) +spider_agent/ AI-powered web automation agent (LLM integration, skills) +spider_cli/ Command-line interface +spider_worker/ Distributed crawling worker +spider_utils/ CSS/XPath scraping utilities +spider_agent_html/ HTML extraction for agent pipelines +spider_agent_types/ Shared types for the agent crate +examples/ 64 runnable examples covering most features +benches/ Criterion benchmarks +``` + +## How to Contribute + +### Reporting Bugs + +Open an issue at [github.com/spider-rs/spider/issues](https://github.com/spider-rs/spider/issues) with: + +- Steps to reproduce +- Expected vs. actual behavior +- Spider version, Rust version, and OS +- A minimal code example or URL that triggers the bug + +### Suggesting Features + +Open an issue describing: + +- The use case and why it matters +- A proposed API or behavior +- A practical example showing how it would be used + +### Pull Requests + +1. Fork the repo and create a branch from `master` +2. Make your changes +3. Add or update tests for your changes +4. Run `cargo test` and ensure everything passes +5. Run `cargo fmt` to format your code +6. Open a pull request against `master` + +#### PR Guidelines + +- **One PR per feature or fix** — keep changes focused +- **Add tests** — new features and bug fixes need test coverage. If testing is difficult, mention it in the PR and we'll help +- **Update docs** — if your change affects public API or behavior, update the relevant README or doc comments +- **Write clear commit messages** — describe what changed and why +- **Keep PRs small** — smaller PRs are easier to review and merge faster + +### Feature Flags + +Spider uses feature flags extensively. When adding new functionality: + +- Gate optional dependencies behind a feature flag +- Add the flag to `spider/Cargo.toml` with a descriptive comment +- Document the flag in the crate's README if it's user-facing +- Make sure the default feature set still compiles: `cargo check --no-default-features` + +### Testing + +```bash +# Run all tests +cargo test + +# Run tests for a specific crate +cargo test -p spider +cargo test -p spider_cli + +# Run tests with a specific feature +cargo test --features chrome +cargo test --features cache + +# Run benchmarks +cargo bench +``` + +## Code Style + +- Follow standard Rust conventions (`cargo fmt`, `cargo clippy`) +- Use descriptive variable and function names +- Keep functions focused — prefer small functions over large ones +- Add doc comments (`///`) to public APIs + +## Questions? + +- Open a [GitHub Discussion](https://github.com/spider-rs/spider/discussions) for questions or ideas +- Join [Discord](https://discord.spider.cloud) for real-time help + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). diff --git a/Cargo.lock b/Cargo.lock index 2bde539a6..78d1e5762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,1097 +1,10965 @@ -[root] -name = "spider" -version = "0.1.0" +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ - "reqwest 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "scraper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ab_glyph_rasterizer", + "owned_ttf_parser", ] [[package]] -name = "adler32" -version = "1.0.2" +name = "ab_glyph_rasterizer" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] -name = "advapi32-sys" -version = "0.2.0" +name = "adblock" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698c1e831aaee3d583efe775790acc7d59fd99d8060e7385b99d2d369a5cdca8" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "addr", + "arrayvec", + "base64 0.22.1", + "bitflags 2.11.0", + "flatbuffers", + "idna", + "itertools 0.13.0", + "memchr", + "once_cell", + "percent-encoding", + "precomputed-hash", + "regex", + "rustc-hash 1.1.0", + "seahash", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", ] [[package]] -name = "base64" -version = "0.9.0" +name = "addr" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef" dependencies = [ - "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "psl", + "psl-types", ] [[package]] -name = "bitflags" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "0.9.1" +name = "addr2line" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] [[package]] -name = "bitflags" -version = "1.0.1" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] -name = "build_const" -version = "0.2.0" +name = "adobe-cmap-parser" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3" +dependencies = [ + "pom", +] [[package]] -name = "byteorder" -version = "1.2.1" +name = "aes" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] [[package]] -name = "bytes" -version = "0.4.5" +name = "ahash" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", ] [[package]] -name = "cc" -version = "1.0.3" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] [[package]] -name = "cfg-if" -version = "0.1.2" +name = "aligned" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] [[package]] -name = "core-foundation" -version = "0.2.3" +name = "aligned-vec" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" dependencies = [ - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "equator", ] [[package]] -name = "core-foundation-sys" -version = "0.2.3" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] -name = "crc" -version = "1.7.0" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "alloc-no-stdlib", ] [[package]] -name = "crypt32-sys" -version = "0.2.0" +name = "alloca" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", ] [[package]] -name = "cssparser" -version = "0.5.8" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "debug_unreachable" -version = "0.1.1" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] -name = "dtoa" -version = "0.4.2" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "ego-tree" -version = "0.1.1" +name = "anndists" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8238f99889a837cd6641360f9f3ead18f70b07bf6ce1f04a319bc6bd8a2f48f1" +dependencies = [ + "anyhow", + "cfg-if", + "cpu-time", + "env_logger", + "lazy_static", + "log", + "num-traits", + "num_cpus", + "rayon", +] [[package]] -name = "encoding" -version = "0.2.33" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ - "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "anstyle", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" +name = "anstream" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "anstyle", + "anstyle-parse 1.0.0", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "encoding-index-korean" -version = "1.20141219.5" +name = "anstyle" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8parse", ] [[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" +name = "anstyle-parse" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8parse", ] [[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-sys 0.61.2", ] [[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ - "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] -name = "encoding_index_tests" -version = "0.1.4" +name = "antidote" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" [[package]] -name = "fnv" -version = "1.0.6" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "ar_archive_writer" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "arbitrary" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_arbitrary", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "arc-swap" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] [[package]] -name = "futf" -version = "0.1.3" +name = "arg_enum_proc_macro" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ - "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "futures" -version = "0.1.17" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "futures-cpupool" -version = "0.1.8" +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable_deref_trait", ] [[package]] -name = "html5ever" -version = "0.5.4" +name = "async-compression" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", - "tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "httparse" -version = "1.2.3" +name = "async-convert" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d416feee97712e43152cd42874de162b8f9b77295b1c85e5d92725cc8310bae" +dependencies = [ + "async-trait", +] [[package]] -name = "hyper" -version = "0.11.11" +name = "async-lock" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "event-listener", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "hyper-tls" -version = "0.1.2" +name = "async-openai" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b17e44510f88a904cb3102a020d7ea11929dcddadfaff756e71112cb2cb7bd0" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "async-convert", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder", + "eventsource-stream", + "futures", + "rand 0.8.5", + "reqwest 0.12.28", + "reqwest-eventsource", + "secrecy 0.8.0", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", ] [[package]] -name = "idna" -version = "0.1.4" +name = "async-openai" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc48c3deb4ad9a2ee8c8e364c79eb0f74e69e17ed7e883d55988b90ea44fe986" dependencies = [ - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "async-openai-macros", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder", + "eventsource-stream", + "futures", + "getrandom 0.3.4", + "rand 0.9.2", + "reqwest 0.12.28", + "reqwest-eventsource", + "secrecy 0.10.3", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "url", ] [[package]] -name = "iovec" +name = "async-openai-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81872a8e595e8ceceab71c6ba1f9078e313b452a1e31934e6763ef5d308705e4" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "itoa" -version = "0.3.4" +name = "async-stream" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "async-stream-impl" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "language-tags" -version = "0.2.2" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "lazy_static" -version = "0.2.11" +name = "async_job" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c007a28aae283e3d003df0a6eec9756cb417cb0013fc17716fa195c663944c6" +dependencies = [ + "async-trait", + "chrono", + "cron 0.12.1", + "lazy_static", + "log", + "tokio", +] [[package]] -name = "lazy_static" -version = "1.0.0" +name = "atoi" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] [[package]] -name = "lazycell" -version = "0.6.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "libc" -version = "0.2.35" +name = "auto_encoder" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6364e11e0270035ec392151a54f1476e6b3612ef9f4fe09d35e72a8cebcb65" +dependencies = [ + "chardetng", + "encoding_rs", + "percent-encoding", + "phf 0.11.3", + "phf_codegen 0.11.3", +] [[package]] -name = "libflate" -version = "0.1.14" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "log" -version = "0.3.9" +name = "av-scenechange" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" dependencies = [ - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", ] [[package]] -name = "log" -version = "0.4.1" +name = "av1-grain" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" dependencies = [ - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", ] [[package]] -name = "mac" -version = "0.1.1" +name = "avif-serialize" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] [[package]] -name = "matches" -version = "0.1.6" +name = "aws-config" +version = "1.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "sha1", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] [[package]] -name = "mime" -version = "0.3.5" +name = "aws-credential-types" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" dependencies = [ - "unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", ] [[package]] -name = "mime_guess" -version = "2.0.0-alpha.3" +name = "aws-lc-rs" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ - "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-lc-sys", + "zeroize", ] [[package]] -name = "mio" -version = "0.6.12" +name = "aws-lc-sys" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "miow" -version = "0.2.1" +name = "aws-runtime" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http 0.63.6", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", ] [[package]] -name = "native-tls" -version = "0.1.4" +name = "aws-sdk-s3" +version = "1.119.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" dependencies = [ - "openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http 0.62.6", + "aws-smithy-json 0.61.9", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "lru 0.12.5", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", ] [[package]] -name = "net2" -version = "0.2.31" +name = "aws-sdk-sso" +version = "1.96.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64a6eded248c6b453966e915d32aeddb48ea63ad17932682774eb026fbef5b1" dependencies = [ - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", ] [[package]] -name = "nodrop" -version = "0.1.12" +name = "aws-sdk-ssooidc" +version = "1.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db96d720d3c622fcbe08bae1c4b04a72ce6257d8b0584cb5418da00ae20a344f" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] [[package]] -name = "num-traits" -version = "0.1.41" +name = "aws-sdk-sts" +version = "1.100.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fafbdda43b93f57f699c5dfe8328db590b967b8a820a13ccdd6687355dfcc7ca" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-json 0.62.5", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] [[package]] -name = "num_cpus" -version = "1.8.0" +name = "aws-sigv4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http 0.63.6", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", ] [[package]] -name = "openssl" -version = "0.9.23" +name = "aws-smithy-async" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" dependencies = [ - "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util", + "pin-project-lite", + "tokio", ] [[package]] -name = "openssl-sys" -version = "0.9.23" +name = "aws-smithy-checksums" +version = "0.63.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" dependencies = [ - "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-http 0.62.6", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", ] [[package]] -name = "percent-encoding" -version = "1.0.1" +name = "aws-smithy-eventstream" +version = "0.60.20" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] [[package]] -name = "phf" -version = "0.7.21" +name = "aws-smithy-http" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" dependencies = [ - "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "phf_codegen" -version = "0.7.21" +name = "aws-smithy-http" +version = "0.63.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" dependencies = [ - "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "phf_generator" -version = "0.7.21" +name = "aws-smithy-http-client" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" dependencies = [ - "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", ] [[package]] -name = "phf_shared" -version = "0.7.21" +name = "aws-smithy-json" +version = "0.61.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" dependencies = [ - "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-types", ] [[package]] -name = "pkg-config" -version = "0.3.9" +name = "aws-smithy-json" +version = "0.62.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +dependencies = [ + "aws-smithy-types", +] [[package]] -name = "quickersort" -version = "2.2.0" +name = "aws-smithy-observability" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" dependencies = [ - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-runtime-api", ] [[package]] -name = "rand" -version = "0.3.20" +name = "aws-smithy-query" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "redox_syscall" -version = "0.1.37" +name = "aws-smithy-runtime" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028999056d2d2fd58a697232f9eec4a643cf73a71cf327690a7edad1d2af2110" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http 0.63.6", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] [[package]] -name = "relay" -version = "0.1.0" +name = "aws-smithy-runtime-api" +version = "1.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ab3c9c29791ba4ba02b780a3049e21ec63dabda09268b175272c3733a79e6" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", ] [[package]] -name = "reqwest" -version = "0.8.2" +name = "aws-smithy-types" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b1117b3b2bbe166d11199b540ceed0d0f7676e36e7b962b5a437a9971eac75" dependencies = [ - "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", ] [[package]] -name = "rustc-serialize" -version = "0.3.24" +name = "aws-smithy-xml" +version = "0.60.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] [[package]] -name = "safemem" -version = "0.2.0" +name = "aws-types" +version = "1.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] [[package]] -name = "schannel" -version = "0.1.9" +name = "axum" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", ] [[package]] -name = "scoped-tls" -version = "0.1.0" +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] [[package]] -name = "scraper" +name = "backoff" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "cssparser 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ego-tree 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "html5ever 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "selectors 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", - "tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "getrandom 0.2.17", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", ] [[package]] -name = "secur32-sys" -version = "0.2.0" +name = "backtrace" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", ] [[package]] -name = "security-framework" -version = "0.1.16" +name = "base16ct" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] -name = "security-framework-sys" -version = "0.1.16" +name = "base64" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "selectors" -version = "0.5.9" +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cssparser 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quickersort 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "outref", + "vsimd", ] [[package]] -name = "serde" -version = "0.8.23" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "serde" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "benches" +version = "0.0.0" +dependencies = [ + "criterion", + "spider 2.47.22", + "spider_utils 2.47.22", +] [[package]] -name = "serde_json" -version = "1.0.9" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] -name = "serde_urlencoded" -version = "0.5.1" +name = "bindgen" +version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.117", ] [[package]] -name = "siphasher" -version = "0.2.2" +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.117", +] [[package]] -name = "slab" -version = "0.3.0" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] [[package]] -name = "slab" -version = "0.4.0" +name = "bit-vec" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] -name = "smallvec" -version = "0.1.8" +name = "bit_field" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] -name = "smallvec" -version = "0.2.1" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "string_cache" -version = "0.2.29" +name = "bitflags" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ - "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_core", ] [[package]] -name = "take" -version = "0.1.0" +name = "bitstream-io" +version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] [[package]] -name = "tempdir" -version = "0.3.5" +name = "blake3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ - "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", ] [[package]] -name = "tendril" -version = "0.2.4" +name = "block" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "futf 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] -name = "time" -version = "0.1.39" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] -name = "tokio-core" -version = "0.1.11" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", ] [[package]] -name = "tokio-io" -version = "0.1.4" +name = "boring-sys2" +version = "4.15.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6c876a958d17057d9d3a31075bb9609237413a1fe665432782c31ef596eb6b" dependencies = [ - "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "bindgen 0.72.1", + "cmake", + "fs_extra", + "fslock", ] [[package]] -name = "tokio-proto" -version = "0.1.1" +name = "boring2" +version = "4.15.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4470e96bd94533c2f88c08be95a8e6d2d37a3b497a773b0a46273a376978f00" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 2.11.0", + "boring-sys2", + "brotli", + "flate2", + "foreign-types 0.5.0", + "libc", + "openssl-macros", + "zstd", ] [[package]] -name = "tokio-service" -version = "0.1.0" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", ] [[package]] -name = "tokio-tls" -version = "0.1.4" +name = "brotli-decompressor" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ - "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] -name = "unicase" -version = "1.4.2" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ - "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "alloc-no-stdlib", + "alloc-stdlib", ] [[package]] -name = "unicase" -version = "2.1.0" +name = "bstr" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ - "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", + "regex-automata", + "serde", ] [[package]] -name = "unicode-bidi" -version = "0.3.4" +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecount" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bytemuck_derive", ] [[package]] -name = "unicode-normalization" -version = "0.1.5" +name = "bytemuck_derive" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "unreachable" -version = "0.1.1" +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", ] [[package]] -name = "url" -version = "1.6.0" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "either", ] [[package]] -name = "utf-8" +name = "cacache" +version = "13.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5063741c7b2e260bbede781cf4679632dd90e2718e99f7715e46824b65670b" +dependencies = [ + "digest", + "either", + "futures", + "hex", + "libc", + "memmap2 0.5.10", + "miette", + "reflink-copy", + "serde", + "serde_derive", + "serde_json", + "sha1", + "sha2", + "ssri", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "walkdir", +] + +[[package]] +name = "candle-core" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ccf5ee3532e66868516d9b315f73aec9f34ea1a37ae98514534d458915dbf1" +dependencies = [ + "byteorder", + "candle-metal-kernels", + "gemm 0.17.1", + "half", + "memmap2 0.9.10", + "metal 0.27.0", + "num-traits", + "num_cpus", + "rand 0.9.2", + "rand_distr 0.5.1", + "rayon", + "safetensors", + "thiserror 1.0.69", + "ug", + "ug-metal", + "yoke 0.7.5", + "zip", +] + +[[package]] +name = "candle-metal-kernels" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c85c21827c28db94e7112e364abe7e0cf8d2b022c014edf08642be6b94f21e" +dependencies = [ + "metal 0.27.0", + "once_cell", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "candle-nn" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1160c3b63f47d40d91110a3e1e1e566ae38edddbbf492a60b40ffc3bc1ff38" +dependencies = [ + "candle-core", + "candle-metal-kernels", + "half", + "metal 0.27.0", + "num-traits", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "candle-transformers" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a0900d49f8605e0e7e6693a1f560e6271279de98e5fa369e7abf3aac245020" +dependencies = [ + "byteorder", + "candle-core", + "candle-nn", + "fancy-regex", + "num-traits", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "serde_plain", + "tracing", +] + +[[package]] +name = "case_insensitive_string" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6260a8cc32da29c626ce456aad5c579c3a36294722f00cccceeb64968820f7" +dependencies = [ + "compact_str", + "serde", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 7.1.3", ] [[package]] -name = "uuid" -version = "0.5.1" +name = "cff-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f5b6e9141c036f3ff4ce7b2f7e432b0f00dee416ddcd4f17741d189ddc2e9d" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chardetng" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" dependencies = [ - "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "encoding_rs", + "memchr", ] [[package]] -name = "vcpkg" -version = "0.2.2" +name = "chromey" +version = "2.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183679faab96df2da020aa6e56eb9302cae67ce21cc00adbba60254c6d23a262" +dependencies = [ + "adblock", + "aho-corasick", + "auto_encoder", + "base64 0.22.1", + "blake3", + "case_insensitive_string", + "dashmap", + "dunce", + "fnv", + "futures", + "futures-timer", + "hashbrown 0.15.5", + "hex", + "http-cache", + "http-cache-reqwest", + "http-cache-semantics", + "http-global-cache", + "lazy_static", + "lol_html", + "phf 0.11.3", + "pin-project-lite", + "rand 0.9.2", + "reqwest 0.13.2", + "serde", + "serde_json", + "serde_stacker", + "sonic-rs", + "spider_chromiumoxide_cdp", + "spider_chromiumoxide_types", + "spider_fingerprint", + "spider_firewall", + "spider_network_blocker", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.28.0", + "tracing", + "url", + "winreg 0.52.0", +] [[package]] -name = "version_check" +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "chrono_utils" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f69ed74e2117892a1a4e05d31f6612178e8e827bfbd83bbf8ca8c1bcfbda710" +dependencies = [ + "chrono", +] [[package]] -name = "void" -version = "1.0.2" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] [[package]] -name = "winapi" -version = "0.2.8" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] -name = "winapi" -version = "0.3.3" +name = "ciborium-ll" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ - "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ciborium-io", + "half", ] [[package]] -name = "winapi-build" -version = "0.1.1" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.3.2" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.3.2" +name = "clap" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream 1.0.0", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "console-api" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "hyper-util", + "prost", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" -"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" -"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4" -"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" -"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" -"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9" -"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" -"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" -"checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719" -"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" -"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" -"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" -"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7" -"checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" -"checksum cssparser 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9447212c775f9e07f598fc5263fac48c7cea7a1c236646e4c9eb6070f13a85fd" -"checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3" -"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" -"checksum ego-tree 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf94c44c4d72ff9a371d5f8143a6c12adf0d6a0e52b2ae5246d648ffeac9ac4d" -"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -"checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" -"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futf 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "51f93f3de6ba1794dcd5810b3546d004600a59a98266487c8407bc4b24e398f3" -"checksum futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "118b49cac82e04121117cbd3121ede3147e885627d82c4546b87c702debb90c1" -"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum html5ever 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd04d31858b8fd8ac9d55570da8c822bc6defd2c1ac18a47cb70fc280f42b432" -"checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" -"checksum hyper 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4aee35c6e7d3f7051be6f0a93ea9d03924ed0db7de0cd04a7d500bfaebf79c9c" -"checksum hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c81fa95203e2a6087242c38691a0210f23e9f3f8f944350bd676522132e2985" -"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" -"checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7" -"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" -"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" -"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" -"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" -"checksum libflate 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "1a429b86418868c7ea91ee50e9170683f47fd9d94f5375438ec86ec3adb74e8e" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" -"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" -"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd" -"checksum mime_guess 2.0.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "013572795763289e14710c7b279461295f2673b2b338200c235082cd7ca9e495" -"checksum mio 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "75f72a93f046f1517e3cfddc0a096eb756a2ba727d36edc8227dee769a50a9b0" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04b781c9134a954c84f0594b9ab3f5606abc516030388e8511887ef4c204a1e5" -"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" -"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" -"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" -"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" -"checksum openssl 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "169a4b9160baf9b9b1ab975418c673686638995ba921683a7f1e01470dcb8854" -"checksum openssl-sys 0.9.23 (registry+https://github.com/rust-lang/crates.io-index)" = "2200ffec628e3f14c39fc0131a301db214f1a7d584e36507ee8700b0c7fb7a46" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc" -"checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f" -"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03" -"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2" -"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" -"checksum quickersort 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "14ae8f367c38c78abd03114e524b55a817885446662413fbca951f42848450c5" -"checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" -"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" -"checksum relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f301bafeb60867c85170031bdb2fcf24c8041f33aee09e7b116a58d4e9f781c5" -"checksum reqwest 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3161ca63fd11ce36c7718af239e6492a25a3dbfcec54240f959b9d816cf42413" -"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" -"checksum schannel 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4330c2e874379fbd28fa67ba43239dbe8c7fb00662ceb1078bd37474f08bf5ce" -"checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" -"checksum scraper 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4e6e16469ead09e3fd2da8aae95e5c3acc729789b59794f1f96079f3a03c2937" -"checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" -"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" -"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" -"checksum selectors 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2d92facb7ec45ccd96ee9ceaf3cc9692d2c9094f147f373d162a5e1b4742a480" -"checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" -"checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb" -"checksum serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce0fd303af908732989354c6f02e05e2e6d597152870f2c6990efb0577137480" -"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" -"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" -"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" -"checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410" -"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" -"checksum string_cache 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "f585562982abf1301fa97bd2226a3c4c5712b8beb9bcd16ed72b5e96810f8657" -"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" -"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" -"checksum tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce04c250d202db8004921e3d3bc95eaa4f2126c6937a428ae39d12d0e38df62" -"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" -"checksum tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c87c27560184212c9dc45cd8f38623f37918248aad5b58fb65303b5d07a98c6e" -"checksum tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "514aae203178929dbf03318ad7c683126672d4d96eccb77b29603d33c9e25743" -"checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" -"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" -"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913" -"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" -"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" -"checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2" -"checksum utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9aee9ba280438b56d1ebc5329f2094f0ff457f811eeeff0b278d75aa99db400" -"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" -"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" -"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" -"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpu-time" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc-fast" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" +dependencies = [ + "crc", + "digest", + "rand 0.9.2", + "regex", + "rustversion", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "tokio", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "cron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" +dependencies = [ + "chrono", + "nom 7.1.3", + "once_cell", +] + +[[package]] +name = "cron" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740" +dependencies = [ + "chrono", + "once_cell", + "winnow 0.6.26", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dyn-stack" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e53799688f5632f364f8fb387488dd05db9fe45db7011be066fc20e7027f8b" +dependencies = [ + "bytemuck", + "reborrow", +] + +[[package]] +name = "dyn-stack" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8" +dependencies = [ + "bytemuck", + "dyn-stack-macros", +] + +[[package]] +name = "dyn-stack-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" + +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher", +] + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve", + "rfc6979", + "signature 1.6.4", +] + +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff", + "generic-array", + "group", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +dependencies = [ + "anstream 0.6.21", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "euclid" +version = "0.20.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom 7.1.3", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fast_html2md" +version = "0.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3a0122fee1bcf6bb9f3d73782e911cce69d95b76a5e29e930af92cd4a8e4e3" +dependencies = [ + "auto_encoder", + "futures-util", + "lazy_static", + "lol_html", + "percent-encoding", + "regex", + "url", +] + +[[package]] +name = "fast_html5ever" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb09004fadff4b73e882edeb42527d3789009ca821467169168867eeb71681d" +dependencies = [ + "fast_markup5ever", + "log", + "mac", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fast_markup5ever" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f899f3b7e9bf005393dd90318d7e721e0ecd230073b675a6737ed502ecff557c" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "faststr" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca7d44d22004409a61c393afb3369c8f7bb74abcae49fe249ee01dcc3002113" +dependencies = [ + "bytes", + "rkyv", + "serde", + "simdutf8", +] + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ffmpeg-next" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9" +dependencies = [ + "bitflags 2.11.0", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e9c75ebd4463de9d8998fb134ba26347fe5faee62fabf0a4b4d41bd500b4ad" +dependencies = [ + "bindgen 0.70.1", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags 2.11.0", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flexbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc752b3d049e0705749b9999d0b130d6cf62935bc7762fd3bdb7636047abe43" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "num_enum 0.5.11", + "serde", + "serde_derive", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fst" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "g2gen" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a7e0eb46f83a20260b850117d204366674e85d3a908d90865c78df9a6b1dfc" +dependencies = [ + "g2poly", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "g2p" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539e2644c030d3bf4cd208cb842d2ce2f80e82e6e8472390bcef83ceba0d80ad" +dependencies = [ + "g2gen", + "g2poly", +] + +[[package]] +name = "g2poly" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312d2295c7302019c395cfb90dacd00a82a2eabd700429bba9c7a3f38dbbe11b" + +[[package]] +name = "gemini-rust" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9b9a88aad13b28ae83c5b8db1d8662f0e1e9114272861d7f6cd2c8419ddc22" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.22.1", + "eventsource-stream", + "futures", + "futures-util", + "mime", + "mime_guess", + "reqwest 0.12.28", + "schemars", + "serde", + "serde_json", + "snafu", + "strum", + "strum_macros", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "gemm" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-c32 0.18.2", + "gemm-c64 0.18.2", + "gemm-common 0.18.2", + "gemm-f16 0.18.2", + "gemm-f32 0.18.2", + "gemm-f64 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-common" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" +dependencies = [ + "bytemuck", + "dyn-stack 0.10.0", + "half", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.18.22", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.2", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.21.5", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", + "sysctl 0.6.0", +] + +[[package]] +name = "gemm-f16" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.18.2", + "gemm-f32 0.18.2", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" +dependencies = [ + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.6.0", + "seq-macro", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", + "num-traits", + "rand 0.9.2", + "rand_distr 0.5.1", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom 7.1.3", + "num-traits", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hf-hub" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b780635574b3d92f036890d8373433d6f9fc7abb320ee42a5c25897fc8ed732" +dependencies = [ + "dirs", + "indicatif", + "log", + "native-tls", + "rand 0.8.5", + "serde", + "serde_json", + "thiserror 1.0.69", + "ureq", +] + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.2", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hnsw_rs" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5258f079b97bf2e8311ff9579e903c899dcbac0d9a138d62e9a066778bd07" +dependencies = [ + "anndists", + "anyhow", + "bincode", + "cfg-if", + "cpu-time", + "env_logger", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "lazy_static", + "log", + "mmap-rs", + "num-traits", + "num_cpus", + "parking_lot", + "rand 0.9.2", + "rayon", + "serde", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "htr" +version = "0.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0e5190458ce24b51fd95e34be40d23b8ec15e1d6cef76a8f8f321744efefc8" +dependencies = [ + "convert_case 0.5.0", + "lazy_static", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "http-cache" +version = "1.0.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebe156dd658fb9031e773f54eed6ae2b5bd12b3ddfb71e456bbc10ae63f4012" +dependencies = [ + "async-trait", + "bytes", + "cacache", + "futures", + "hex", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "http-cache-semantics", + "httpdate", + "log", + "moka", + "pin-project-lite", + "postcard", + "serde", + "tokio", + "url", +] + +[[package]] +name = "http-cache-reqwest" +version = "1.0.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42fed00fde0c81a9e473920a04aeaf879c1abb8a5435769bea707c7d776e8d1c" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "http-cache", + "http-cache-semantics", + "reqwest 0.13.2", + "reqwest-middleware", + "url", +] + +[[package]] +name = "http-cache-semantics" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0603b99309a1e404c2c0945650c0f775b50bb72ac90ae570cb93f9ff05d9efe7" +dependencies = [ + "http 1.4.0", + "http-serde", + "serde", + "time", +] + +[[package]] +name = "http-global-cache" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d2436dd8815c607f6443f0740e038bbe72d09ec1d1a660c583d78d54daff4d" +dependencies = [ + "http-cache", + "http-cache-reqwest", + "lazy_static", +] + +[[package]] +name = "http-serde" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" +dependencies = [ + "http 1.4.0", + "serde", +] + +[[package]] +name = "http2" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e23815f8ec982e1452e1d0fda921ec20a9187fb610ad003c90cc5abd65b2c4" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.6", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration 0.7.0", + "tokio", + "tower-service", + "tracing", + "windows-registry 0.6.1", +] + +[[package]] +name = "hyper2" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a1bb25e0cbdbe7b21f8ef6c3c4785f147d79e7ded438b5ee2b70e380183294" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http2", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke 0.8.1", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke 0.8.1", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error 2.0.1", +] + +[[package]] +name = "imageproc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2393fb7808960751a52e8a154f67e7dd3f8a2ef9bd80d1553078a7b4e8ed3f0d" +dependencies = [ + "ab_glyph", + "approx", + "getrandom 0.2.17", + "image", + "itertools 0.12.1", + "nalgebra 0.32.6", + "num", + "rand 0.8.5", + "rand_distr 0.4.3", + "rayon", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width 0.2.2", + "web-time", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "instant-distance" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c619cdaa30bb84088963968bee12a45ea5fbbf355f2c021bcd15589f5ca494a" +dependencies = [ + "num_cpus", + "ordered-float", + "parking_lot", + "rand 0.8.5", + "rayon", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "io-uring" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "llm_json" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6a0ee634dc2211a67b3ee239fac8098647352fc73a39b50c57fbf7fb7a2928" +dependencies = [ + "clap", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "llm_models_spider" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff3eaea579ea35091f7899d095d96a03ccae3e72ee5ad7b69b7b38b0fd7694" + +[[package]] +name = "llm_readability" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd03f1473eaaf56fe7dccef40ad64626b98f4dba23af0ab7fcfc7d2d6b6845c" +dependencies = [ + "auto_encoder", + "html5ever", + "markup5ever 0.13.0", + "markup5ever_rcdom", + "regex", + "url", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lol_html" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff94cb6aef6ee52afd2c69331e9109906d855e82bd241f3110dfdf6185899ab" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cssparser 0.36.0", + "encoding_rs", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "memchr", + "mime", + "precomputed-hash", + "selectors 0.33.0", + "thiserror 2.0.18", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lopdf" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7" +dependencies = [ + "aes", + "bitflags 2.11.0", + "cbc", + "chrono", + "ecb", + "encoding_rs", + "flate2", + "indexmap 2.13.0", + "itoa", + "jiff", + "log", + "md-5", + "nom 8.0.0", + "nom_locate", + "rand 0.9.2", + "rangemap", + "rayon", + "sha2", + "stringprep", + "thiserror 2.0.18", + "time", + "weezl", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "macro_rules_attribute" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d581ff8be69d08a2efa23a959d81aa22b739073f749f067348bd4f4ba4b69195" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever 0.12.1", + "tendril", + "xml5ever", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", + "stable_deref_trait", +] + +[[package]] +name = "memvid-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f25949f7825bf08fd75493286410455bd062cb95f570fe82c46c0f57b929f8" +dependencies = [ + "anyhow", + "async-openai 0.25.0", + "base64 0.22.1", + "bincode", + "byteorder", + "candle-core", + "candle-nn", + "candle-transformers", + "chrono", + "clap", + "env_logger", + "ffmpeg-next", + "flate2", + "futures", + "half", + "hf-hub", + "hnsw_rs", + "image", + "imageproc", + "indicatif", + "instant-distance", + "log", + "lopdf", + "lru 0.12.5", + "memmap2 0.9.10", + "ndarray", + "pdf-extract", + "qrcode", + "rayon", + "regex", + "reqwest 0.12.28", + "rqrr", + "rusqlite", + "safetensors", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokenizers", + "tokio", + "unicode-normalization", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror 1.0.69", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mmap-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ecce9d566cb9234ae3db9e249c8b55665feaaf32b0859ff1e27e310d2beb3d8" +dependencies = [ + "bitflags 2.11.0", + "combine", + "libc", + "mach2", + "nix", + "sysctl 0.6.0", + "thiserror 2.0.18", + "widestring", + "windows 0.48.0", +] + +[[package]] +name = "moka" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba 0.8.1", + "typenum", +] + +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "rand 0.8.5", + "rand_distr 0.4.3", + "simba 0.9.1", + "typenum", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom_locate" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" +dependencies = [ + "bytecount", + "memchr", + "nom 8.0.0", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive 0.7.5", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.11.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pdf-extract" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2f44c6c642e359e2fe7f662bf5438db3811b6b4be60afc6de04b619ce51e1a" +dependencies = [ + "adobe-cmap-parser", + "cff-parser", + "encoding_rs", + "euclid", + "log", + "lopdf", + "postscript", + "type1-encoding-parser", + "unicode-normalization", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "peresil" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pom" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "postscript" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.4+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "psl" +version = "2.1.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc151a7aba9221a5413baff8677aa98f450621cf7bd45ad17ca81d57a7f26ac" +dependencies = [ + "psl-types", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "pulp" +version = "0.18.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" +dependencies = [ + "bytemuck", + "libm", + "num-complex", + "reborrow", +] + +[[package]] +name = "pulp" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +dependencies = [ + "image", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", + "serde", + "tokio", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.37", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rancor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.2", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error 2.0.1", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-cond" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059f538b55efd2309c9794130bc149c6a553db90e9d99c2030785c82f0bd7df9" +dependencies = [ + "either", + "itertools 0.11.0", + "rayon", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reflink-copy" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13362233b147e57674c37b802d216b7c5e3dcccbed8967c84f0d8d223868ae27" +dependencies = [ + "cfg-if", + "libc", + "rustix 1.1.4", + "windows 0.62.2", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rend" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.4.2", + "web-sys", + "webpki-roots 1.0.6", +] + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store 0.22.1", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.13", + "hickory-resolver", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom 7.1.3", + "pin-project-lite", + "reqwest 0.12.28", + "thiserror 1.0.69", +] + +[[package]] +name = "reqwest-middleware" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199dda04a536b532d0cc04d7979e39b1c763ea749bf91507017069c00b96056f" +dependencies = [ + "anyhow", + "async-trait", + "http 1.4.0", + "reqwest 0.13.2", + "thiserror 2.0.18", + "tower-service", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +dependencies = [ + "bytes", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rqrr" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2260da7f69877ba68c49a0c2d9946829848236c708dd40d2a6baf8c868ee887" +dependencies = [ + "g2p", + "image", + "lru 0.12.5", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink 0.9.1", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs 1.0.6", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "safetensors" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +dependencies = [ + "bitflags 2.11.0", + "cssparser 0.31.2", + "derive_more 0.99.20", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen 0.10.0", + "precomputed-hash", + "servo_arc 0.3.0", + "smallvec", +] + +[[package]] +name = "selectors" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" +dependencies = [ + "bitflags 2.11.0", + "cssparser 0.36.0", + "derive_more 2.1.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash 2.1.1", + "servo_arc 0.4.3", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_stacker" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" +dependencies = [ + "serde", + "serde_core", + "stacker", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "servo_arc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "sitemap" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c697e1d88854f66d7d805e8b532e341661a4b8f49fab419be8e82b0cafdeb4d" +dependencies = [ + "chrono", + "chrono_utils", + "url", + "xml-rs", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "backtrace", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sonic-number" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5661364b38abad49cf1ade6631fcc35d2ccf882a7d68616b4228b7717feb5fba" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "sonic-rs" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16b39f36452a4fa6f14a481b02e9ba5c6842c6a44d0545da967eca122f2eed9" +dependencies = [ + "ahash", + "bumpalo", + "bytes", + "cfg-if", + "faststr", + "itoa", + "ref-cast", + "serde", + "simdutf8", + "sonic-number", + "sonic-simd", + "thiserror 2.0.18", + "zmij", +] + +[[package]] +name = "sonic-simd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f944718c33623919878cf74b4c9361eb3024f635733922b26722b14cd3f8cc" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "spider" +version = "2.47.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d770e1b23232fa0af8aea8d95043dab6bcd730330042f656afa2a28f9fef6d46" +dependencies = [ + "ahash", + "aho-corasick", + "auto_encoder", + "bytes", + "case_insensitive_string", + "h2 0.4.13", + "hashbrown 0.16.1", + "lazy_static", + "libc", + "log", + "lol_html", + "num_cpus", + "percent-encoding", + "phf 0.11.3", + "pin-project-lite", + "quick-xml", + "regex", + "reqwest 0.13.2", + "serde", + "serde_json", + "serde_regex", + "smallvec", + "spider_agent_types 2.47.18", + "spider_fingerprint", + "string-interner", + "string_concat", + "strum", + "tokio", + "tokio-stream", + "tower 0.5.3", + "url", +] + +[[package]] +name = "spider" +version = "2.47.22" +dependencies = [ + "ahash", + "aho-corasick", + "async-openai 0.33.1", + "async-trait", + "async_job", + "auto_encoder", + "base64 0.22.1", + "bytes", + "case_insensitive_string", + "chromey", + "chrono", + "const_format", + "cookie", + "cron 0.15.0", + "dashmap", + "env_logger", + "fastrand", + "flexbuffers", + "gemini-rust", + "h2 0.4.13", + "hashbrown 0.16.1", + "home", + "http 1.4.0", + "http-cache", + "http-cache-reqwest", + "http-cache-semantics", + "http-global-cache", + "httpdate", + "io-uring", + "itertools 0.14.0", + "lazy_static", + "libc", + "llm_json", + "log", + "lol_html", + "moka", + "num_cpus", + "percent-encoding", + "phf 0.11.3", + "pin-project-lite", + "quick-xml", + "rand 0.9.2", + "regex", + "reqwest 0.13.2", + "reqwest-middleware", + "serde", + "serde_json", + "serde_regex", + "sitemap", + "smallvec", + "sonic-rs", + "spider_agent", + "spider_agent_types 2.47.22", + "spider_fingerprint", + "spider_firewall", + "sqlx", + "statrs", + "string-interner", + "string_concat", + "strum", + "sysinfo", + "thirtyfour", + "tiktoken-rs", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", + "ua_generator", + "url", + "which", + "wreq", + "wreq-util", +] + +[[package]] +name = "spider_agent" +version = "2.47.22" +dependencies = [ + "aho-corasick", + "async-openai 0.33.1", + "async-trait", + "aws-config", + "aws-sdk-s3", + "base64 0.22.1", + "chromey", + "dashmap", + "dotenvy", + "env_logger", + "fastrand", + "futures", + "llm_models_spider", + "log", + "lol_html", + "memvid-rs", + "parking_lot", + "reqwest 0.13.2", + "serde", + "serde_json", + "spider_agent_html", + "spider_agent_types 2.47.22", + "spider_skills", + "tempfile", + "thirtyfour", + "thiserror 2.0.18", + "tiktoken-rs", + "tokio", +] + +[[package]] +name = "spider_agent_html" +version = "2.47.22" +dependencies = [ + "aho-corasick", + "lol_html", + "serde", + "serde_json", + "spider_agent_types 2.47.22", +] + +[[package]] +name = "spider_agent_types" +version = "2.47.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7812544c380670f38e7fd78c5f0eb8ceb6c5fb3b291aafbc6173ff4022f8df7f" +dependencies = [ + "aho-corasick", + "llm_models_spider", + "serde", + "serde_json", +] + +[[package]] +name = "spider_agent_types" +version = "2.47.22" +dependencies = [ + "aho-corasick", + "llm_models_spider", + "serde", + "serde_json", +] + +[[package]] +name = "spider_chromiumoxide_cdp" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "425d07927c18fd157c4680cee8c7868e4a9bf4723bda1a4951c3316e3abdea4b" +dependencies = [ + "serde", + "serde_json", + "spider_chromiumoxide_pdl", + "spider_chromiumoxide_types", +] + +[[package]] +name = "spider_chromiumoxide_pdl" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4090793264cfc7533186505f7cc2198ef03325dfcbfe32d0e6814c6ca759051f" +dependencies = [ + "either", + "heck 0.4.1", + "once_cell", + "proc-macro2", + "quote", + "regex", + "serde", + "serde_json", + "spider_chromiumoxide_types", +] + +[[package]] +name = "spider_chromiumoxide_types" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec8ac95a73fa31063b306dcbb48a2b3115fde2ff9152004148eb152844917e9" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "spider_cli" +version = "2.47.22" +dependencies = [ + "clap", + "env_logger", + "failure_derive", + "percent-encoding", + "quote", + "serde_json", + "spider 2.47.22", + "spider_utils 2.47.22", + "tikv-jemallocator", + "tokio", + "unicode-normalization", +] + +[[package]] +name = "spider_examples" +version = "2.37.105" +dependencies = [ + "console-subscriber", + "convert_case 0.6.0", + "env_logger", + "flexbuffers", + "htr", + "serde_json", + "spider 2.47.22", + "spider_transformations", + "spider_utils 2.47.18", +] + +[[package]] +name = "spider_fingerprint" +version = "2.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e26862e5b31d1292804e64d59601af1594b1ab6e0474c2b62dcd447497ae89" +dependencies = [ + "aho-corasick", + "case_insensitive_string", + "http 1.4.0", + "lazy_static", + "phf 0.11.3", + "phf_codegen 0.11.3", + "rand 0.9.2", + "reqwest 0.12.28", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "spider_firewall" +version = "2.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4338af28f9696571924f2b6a1d31170ec382a04bbfac7731f8739643b0ec6b55" +dependencies = [ + "fst", + "hashbrown 0.15.5", + "phf 0.11.3", + "reqwest 0.12.28", + "serde", + "serde_json", + "ua_generator", +] + +[[package]] +name = "spider_network_blocker" +version = "0.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19cf37feaf9b5f9202d060f8cebc213c2833aecd462841749ab3d4d39d1db3ba" +dependencies = [ + "convert_case 0.8.0", + "hashbrown 0.15.5", + "phf 0.11.3", + "phf_codegen 0.11.3", + "serde", + "url", +] + +[[package]] +name = "spider_scraper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323b02db860415a5414ac0a55a10e830df133b7f3cd877178e5889bc9fba01d3" +dependencies = [ + "ahash", + "auto_encoder", + "cssparser 0.31.2", + "ego-tree", + "fast_html5ever", + "hashbrown 0.15.5", + "lazy_static", + "selectors 0.25.0", + "serde", + "smallvec", + "tendril", +] + +[[package]] +name = "spider_skills" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d89b6f84175f3388899aef3fde8def5d7d037e6cf652b93db6a1e6ba425bf3" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "spider_transformations" +version = "2.37.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23d3c922058c56970357725d8ded379188314cbe99fb1611531542f170dbcb0" +dependencies = [ + "aho-corasick", + "auto_encoder", + "fast_html2md", + "html5ever", + "indexmap 1.9.3", + "llm_readability", + "lol_html", + "markup5ever 0.12.1", + "phf 0.11.3", + "phf_codegen 0.11.3", + "regex", + "serde", + "spider 2.47.18", + "spider_scraper", + "tendril", + "thiserror 1.0.69", + "unicode-width 0.2.2", + "url", +] + +[[package]] +name = "spider_utils" +version = "2.47.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0f3b2babd77b2232899c20b044111fae13eec6af4445e5b51727a35f9aec75" +dependencies = [ + "hashbrown 0.16.1", + "lazy_static", + "log", + "spider_scraper", + "sxd-document", + "sxd-xpath", + "tokio-stream", +] + +[[package]] +name = "spider_utils" +version = "2.47.22" +dependencies = [ + "hashbrown 0.16.1", + "indexmap 2.13.0", + "lazy_static", + "log", + "serde", + "spider_scraper", + "sxd-document", + "sxd-xpath", + "tokio", + "tokio-stream", +] + +[[package]] +name = "spider_worker" +version = "2.47.22" +dependencies = [ + "env_logger", + "lazy_static", + "spider 2.47.22", + "warp", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink 0.10.0", + "indexmap 2.13.0", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "rustls 0.23.37", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.117", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.11.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.11.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "ssri" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" +dependencies = [ + "base64 0.21.7", + "digest", + "hex", + "miette", + "serde", + "sha-1", + "sha2", + "thiserror 1.0.69", + "xxhash-rust", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "nalgebra 0.33.2", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "string-interner" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" +dependencies = [ + "hashbrown 0.15.5", + "serde", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_concat" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c3ee6129eec20fed59acf2e9cfb3ffd20d0bbe39fe334c22af0edc56dfe752" + +[[package]] +name = "stringmatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aadc0801d92f0cdc26127c67c4b8766284f52a5ba22894f285e3101fa57d05d" +dependencies = [ + "regex", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sxd-document" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078" +dependencies = [ + "peresil", + "typed-arena", +] + +[[package]] +name = "sxd-xpath" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea" +dependencies = [ + "peresil", + "quick-error 1.2.3", + "sxd-document", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sysctl" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" +dependencies = [ + "bitflags 2.11.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.11.0", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thirtyfour" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f1edfb88bbdf0365c6dc161e8694ce8414c0b79d0d3a3663b4baac62236be0" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.22.1", + "bytes", + "cfg-if", + "const_format", + "futures-util", + "http 1.4.0", + "indexmap 2.13.0", + "paste", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_repr", + "stringmatch", + "thirtyfour-macros", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "thirtyfour-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf0ffc3ba4368e99597bd6afd83f4ff6febad66d9ae541ab46e697d32285fc0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error 2.0.1", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tiktoken-rs" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a19830747d9034cd9da43a60eaa8e552dfda7712424aebf187b7a60126bae0d" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bstr", + "fancy-regex", + "lazy_static", + "regex", + "rustc-hash 1.1.0", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokenizers" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b08cc37428a476fc9e20ac850132a513a2e1ce32b6a31addf2b74fa7033b905" +dependencies = [ + "aho-corasick", + "derive_builder", + "esaxx-rs", + "getrandom 0.2.17", + "indicatif", + "itertools 0.12.1", + "lazy_static", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.8.5", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror 1.0.69", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-boring2" +version = "4.15.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3866209c48e3d69e709a9d6105d7e2aa34e4374bc5dcaec32e0f8e53a28db14a" +dependencies = [ + "boring-sys2", + "boring2", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.21.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.28.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow 0.7.15", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "once_cell", + "regex-automata", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.9.2", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "type1-encoding-parser" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d6cc09e1a99c7e01f2afe4953789311a1c50baebbdac5b477ecf78e2e92a5b" +dependencies = [ + "pom", +] + +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + +[[package]] +name = "typed-builder" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ua_generator" +version = "0.5.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eebe5bf263176003918e31d3789d37dc848f142b72da8eb9cbd8b0e067287e9" +dependencies = [ + "dotenvy", + "fastrand", + "serde", + "serde_json", + "toml", + "ureq", +] + +[[package]] +name = "ug" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03719c61a91b51541f076dfdba45caacf750b230cefaa4b32d6f5411c3f7f437" +dependencies = [ + "gemm 0.18.2", + "half", + "libloading", + "memmap2 0.9.10", + "num", + "num-traits", + "num_cpus", + "rayon", + "safetensors", + "serde", + "thiserror 1.0.69", + "tracing", + "yoke 0.7.5", +] + +[[package]] +name = "ug-metal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a02ddc17bf32f7dcaaf016b6735f7198082b82f122df7b3ca15d8ead5911ccef" +dependencies = [ + "half", + "metal 0.29.0", + "objc", + "serde", + "thiserror 1.0.69", + "ug", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "brotli-decompressor 4.0.3", + "encoding_rs", + "flate2", + "log", + "native-tls", + "once_cell", + "rustls 0.23.37", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper 0.14.32", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.25.0", + "tokio-tungstenite 0.21.0", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.6", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link 0.2.1", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wreq" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "137ed11c4c418fb3dc41db7323ebc49aa9b6fe6a24ce152e6b2de9d6fadd9c8f" +dependencies = [ + "antidote", + "arc-swap", + "async-compression", + "base64 0.22.1", + "boring2", + "bytes", + "cookie", + "cookie_store 0.21.1", + "encoding_rs", + "futures-util", + "hickory-resolver", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper2", + "ipnet", + "linked_hash_set", + "log", + "lru 0.13.0", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "socket2 0.5.10", + "sync_wrapper", + "system-configuration 0.6.1", + "tokio", + "tokio-boring2", + "tokio-socks", + "tokio-util", + "tower 0.5.3", + "tower-service", + "typed-builder", + "url", + "webpki-root-certs 0.26.11", + "windows-registry 0.5.3", +] + +[[package]] +name = "wreq-util" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28b3585973a8923c32c2f916e60b7b0d1cb4dd53e8cb2d7422c0ac001ef2487" +dependencies = [ + "serde", + "typed-builder", + "wreq", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xml" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" + +[[package]] +name = "xml-rs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a56132a0d6ecbe77352edc10232f788fc4ceefefff4cab784a98e0e16b6b51" +dependencies = [ + "xml", +] + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever 0.12.1", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.7.5", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive 0.8.1", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke 0.8.1", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke 0.8.1", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zip" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "indexmap 2.13.0", + "num_enum 0.7.5", + "thiserror 1.0.69", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 59f165021..2f6ca6db6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,14 @@ -[package] -name = "spider" -version = "0.1.0" -authors = ["madeindjs "] +[workspace] +members = [ + "spider", + "spider_worker", + "spider_cli", + "spider_utils", + "spider_agent", + "spider_agent_types", + "spider_agent_html", + # internal + "examples", + "benches"] +resolver = "2" -[dependencies] -reqwest = "0.8.2" -scraper = "0.4.0" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9f26b56be --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Spider Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..2a74465aa --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,57 @@ +# Bazel module for Spider project +# Using Bzlmod with rules_rust crate_universe + +module( + name = "spider", + version = "2.47.22", +) + +# Rust rules (will bring compatible rules_cc) +bazel_dep(name = "rules_rust", version = "0.63.0") + +# Platform definitions +bazel_dep(name = "platforms", version = "1.0.0") + +# Skylib +bazel_dep(name = "bazel_skylib", version = "1.8.2") + +# Setup Rust toolchain +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2021", + versions = ["1.94.0"], +) +use_repo(rust, "rust_toolchains") + +# Setup crate_universe for cargo dependencies +crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") + +# Import dependencies from Cargo.lock +# This will create a repository named "crates_io" +crate.annotation( + crate = "aws-lc-sys", + build_script_env = { + "CRATE_CC_NO_DEFAULTS": "1", + "CFLAGS": "-O0", + "OPT_LEVEL": "0", + }, +) + +crate.from_cargo( + name = "crates_io", + cargo_lockfile = "//:Cargo.lock", + manifests = [ + "//:Cargo.toml", + "//spider:Cargo.toml", + "//spider_cli:Cargo.toml", + "//spider_worker:Cargo.toml", + "//spider_agent:Cargo.toml", + "//spider_agent_types:Cargo.toml", + "//spider_agent_html:Cargo.toml", + "//spider_utils:Cargo.toml", + "//benches:Cargo.toml", + "//examples:Cargo.toml", + ], +) + +use_repo(crate, "crates_io") \ No newline at end of file diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000..38c10b40b --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,8146 @@ +{ + "lockFileVersion": 26, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/9.0.3/MODULE.bazel": "1f98ed015f7e744a745e0df6e898a7c5e83562d6b759dfd475c76456dda5ccea", + "https://bcr.bazel.build/modules/rules_java/9.0.3/source.json": "b038c0c07e12e658135bbc32cc1a2ded6e33785105c9d41958014c592de4593e", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_rust/0.63.0/MODULE.bazel": "4144e1606661c7168d23e8b4e7c5f6fb28ef519d9d5d63e0bd789d1b2a4611f8", + "https://bcr.bazel.build/modules/rules_rust/0.63.0/source.json": "638d4731ad05d31835ba45cffc06e8dc1cca01692a681daf831378cf952ee7e6", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "ABI1D/sbS1ovwaW/kHDoj8nnXjQ0oKU9fzmzEG4iT8o=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "2hLgIvNVTLgxus0ZuXtleBe70intCfo0cHs8qvt6cdM=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + } + } + }, + "@@rules_rust+//crate_universe:extension.bzl%crate": { + "general": { + "bzlTransitiveDigest": "i55KcYayVfrn+/m7fu+BDMHFeKHASbunhw+gwNWhFaM=", + "usagesDigest": "/gnHjPTUdp+6x9Q2d5VH7smK815W51Kb5aYD5Rrgqbs=", + "recordedInputs": [ + "ENV:CARGO_BAZEL_DEBUG \\0", + "ENV:CARGO_BAZEL_GENERATOR_SHA256 \\0", + "ENV:CARGO_BAZEL_GENERATOR_URL \\0", + "ENV:CARGO_BAZEL_ISOLATED \\0", + "ENV:CARGO_BAZEL_REPIN \\0", + "ENV:CARGO_BAZEL_REPIN_ONLY \\0", + "ENV:REPIN \\0", + "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", + "REPO_MAPPING:bazel_features+,bazel_features_version bazel_features++version_extension+bazel_features_version", + "REPO_MAPPING:rules_cc+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_cc+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_cc+,cc_compatibility_proxy rules_cc++compatibility_proxy+cc_compatibility_proxy", + "REPO_MAPPING:rules_cc+,platforms platforms", + "REPO_MAPPING:rules_cc+,rules_cc rules_cc+", + "REPO_MAPPING:rules_cc++compatibility_proxy+cc_compatibility_proxy,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,bazel_features bazel_features+", + "REPO_MAPPING:rules_rust+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_rust+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_rust+,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,rules_rust rules_rust+", + "FILE:@@//Cargo.lock 38e0205dcca3ab9a16d6f30ee2f9d5b7574a6f56c904bf35cab7ccf91eb678fa", + "FILE:@@//Cargo.toml 4c23cde48f2e4414e7702e433afe88f5a60372de7b80c37ba1c58f831d66bf69", + "FILE:@@//spider/Cargo.toml 862cc60801bb307415ccf064bf9961459ea2e3ac1887ec1377ddd5eef7d4e1d4", + "FILE:@@//spider_cli/Cargo.toml 1d0cee581004f8e73ce9de5335043b68b09413e7b5f590a391abf35443f0df0a", + "FILE:@@//spider_worker/Cargo.toml c6c2737698effac52e9879590f25b09887a0fe965cc3c9ab3f9e4d13afee1861", + "FILE:@@//spider_agent/Cargo.toml 0975bcba165c088bc7cdb4ee93702aa53609d42506cbd639f72cbd9e2b1bc3cb", + "FILE:@@//spider_agent_types/Cargo.toml 19b3b06a078973aec7844045d9703afca4d6660b7fc597b0b80189ae3b266ecc", + "FILE:@@//spider_agent_html/Cargo.toml dd7eb0d2dc5b6a07fe0523b25e0f3f6b01291beac1d764181813bb1c97a09803", + "FILE:@@//spider_utils/Cargo.toml 38f1e361fa475131bf2b832fdc7f262a3783a090ca4692e4eb1ee2a9f42a9b1d", + "FILE:@@//benches/Cargo.toml c331ee14886926cab49ba61988f938f2cdb279e4735143e8d55f72f0beb470e0", + "FILE:@@//examples/Cargo.toml 20ab3ae02cec04cd7f042cf6912f527f184eb84e3e0b1cfaff97100acdefd03a" + ], + "generatedRepoSpecs": { + "crates_io": { + "repoRuleId": "@@rules_rust+//crate_universe:extensions.bzl%_generate_repo", + "attributes": { + "contents": { + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"ahash-0.8.12\",\n actual = \"@crates_io__ahash-0.8.12//:ahash\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ahash\",\n actual = \"@crates_io__ahash-0.8.12//:ahash\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aho-corasick-1.1.4\",\n actual = \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aho-corasick\",\n actual = \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"async-trait-0.1.89\",\n actual = \"@crates_io__async-trait-0.1.89//:async_trait\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"async-trait\",\n actual = \"@crates_io__async-trait-0.1.89//:async_trait\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"auto_encoder-0.1.10\",\n actual = \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"auto_encoder\",\n actual = \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"base64-0.22.1\",\n actual = \"@crates_io__base64-0.22.1//:base64\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"base64\",\n actual = \"@crates_io__base64-0.22.1//:base64\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytes-1.11.1\",\n actual = \"@crates_io__bytes-1.11.1//:bytes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bytes\",\n actual = \"@crates_io__bytes-1.11.1//:bytes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"case_insensitive_string-0.2.10\",\n actual = \"@crates_io__case_insensitive_string-0.2.10//:case_insensitive_string\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"case_insensitive_string\",\n actual = \"@crates_io__case_insensitive_string-0.2.10//:case_insensitive_string\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"chromey-2.40.0\",\n actual = \"@crates_io__chromey-2.40.0//:chromiumoxide\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"chromey\",\n actual = \"@crates_io__chromey-2.40.0//:chromiumoxide\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.6.0\",\n actual = \"@crates_io__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@crates_io__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console-subscriber-0.4.1\",\n actual = \"@crates_io__console-subscriber-0.4.1//:console_subscriber\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"console-subscriber\",\n actual = \"@crates_io__console-subscriber-0.4.1//:console_subscriber\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"convert_case-0.6.0\",\n actual = \"@crates_io__convert_case-0.6.0//:convert_case\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"convert_case\",\n actual = \"@crates_io__convert_case-0.6.0//:convert_case\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cookie-0.18.1\",\n actual = \"@crates_io__cookie-0.18.1//:cookie\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cookie\",\n actual = \"@crates_io__cookie-0.18.1//:cookie\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion-0.8.2\",\n actual = \"@crates_io__criterion-0.8.2//:criterion\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"criterion\",\n actual = \"@crates_io__criterion-0.8.2//:criterion\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"dashmap-6.1.0\",\n actual = \"@crates_io__dashmap-6.1.0//:dashmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"dashmap\",\n actual = \"@crates_io__dashmap-6.1.0//:dashmap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"dotenvy-0.15.7\",\n actual = \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"dotenvy\",\n actual = \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"env_logger-0.11.9\",\n actual = \"@crates_io__env_logger-0.11.9//:env_logger\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"env_logger\",\n actual = \"@crates_io__env_logger-0.11.9//:env_logger\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrand-2.3.0\",\n actual = \"@crates_io__fastrand-2.3.0//:fastrand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"fastrand\",\n actual = \"@crates_io__fastrand-2.3.0//:fastrand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"flexbuffers-25.12.19\",\n actual = \"@crates_io__flexbuffers-25.12.19//:flexbuffers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"flexbuffers\",\n actual = \"@crates_io__flexbuffers-25.12.19//:flexbuffers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures-0.3.32\",\n actual = \"@crates_io__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures\",\n actual = \"@crates_io__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"h2-0.4.13\",\n actual = \"@crates_io__h2-0.4.13//:h2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"h2\",\n actual = \"@crates_io__h2-0.4.13//:h2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown-0.16.1\",\n actual = \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hashbrown\",\n actual = \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"home-0.5.12\",\n actual = \"@crates_io__home-0.5.12//:home\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"home\",\n actual = \"@crates_io__home-0.5.12//:home\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"htr-0.5.27\",\n actual = \"@crates_io__htr-0.5.27//:htr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"htr\",\n actual = \"@crates_io__htr-0.5.27//:htr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"io-uring-0.7.11\",\n actual = \"@crates_io__io-uring-0.7.11//:io_uring\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"io-uring\",\n actual = \"@crates_io__io-uring-0.7.11//:io_uring\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy_static-1.5.0\",\n actual = \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lazy_static\",\n actual = \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"libc-0.2.183\",\n actual = \"@crates_io__libc-0.2.183//:libc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"libc\",\n actual = \"@crates_io__libc-0.2.183//:libc\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"llm_models_spider-0.1.42\",\n actual = \"@crates_io__llm_models_spider-0.1.42//:llm_models_spider\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"llm_models_spider\",\n actual = \"@crates_io__llm_models_spider-0.1.42//:llm_models_spider\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.29\",\n actual = \"@crates_io__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@crates_io__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lol_html-2.7.2\",\n actual = \"@crates_io__lol_html-2.7.2//:lol_html\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"lol_html\",\n actual = \"@crates_io__lol_html-2.7.2//:lol_html\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"num_cpus-1.17.0\",\n actual = \"@crates_io__num_cpus-1.17.0//:num_cpus\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"num_cpus\",\n actual = \"@crates_io__num_cpus-1.17.0//:num_cpus\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"parking_lot-0.12.5\",\n actual = \"@crates_io__parking_lot-0.12.5//:parking_lot\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"parking_lot\",\n actual = \"@crates_io__parking_lot-0.12.5//:parking_lot\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"percent-encoding-2.3.2\",\n actual = \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"percent-encoding\",\n actual = \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"phf-0.11.3\",\n actual = \"@crates_io__phf-0.11.3//:phf\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"phf\",\n actual = \"@crates_io__phf-0.11.3//:phf\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pin-project-lite-0.2.17\",\n actual = \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"pin-project-lite\",\n actual = \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quick-xml-0.39.2\",\n actual = \"@crates_io__quick-xml-0.39.2//:quick_xml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quick-xml\",\n actual = \"@crates_io__quick-xml-0.39.2//:quick_xml\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand-0.9.2\",\n actual = \"@crates_io__rand-0.9.2//:rand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand\",\n actual = \"@crates_io__rand-0.9.2//:rand\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex-1.12.3\",\n actual = \"@crates_io__regex-1.12.3//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"regex\",\n actual = \"@crates_io__regex-1.12.3//:regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"reqwest-0.13.2\",\n actual = \"@crates_io__reqwest-0.13.2//:reqwest\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"reqwest\",\n actual = \"@crates_io__reqwest-0.13.2//:reqwest\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-1.0.228\",\n actual = \"@crates_io__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde\",\n actual = \"@crates_io__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json-1.0.149\",\n actual = \"@crates_io__serde_json-1.0.149//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json\",\n actual = \"@crates_io__serde_json-1.0.149//:serde_json\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_regex-1.1.0\",\n actual = \"@crates_io__serde_regex-1.1.0//:serde_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_regex\",\n actual = \"@crates_io__serde_regex-1.1.0//:serde_regex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smallvec-1.15.1\",\n actual = \"@crates_io__smallvec-1.15.1//:smallvec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smallvec\",\n actual = \"@crates_io__smallvec-1.15.1//:smallvec\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_fingerprint-2.38.1\",\n actual = \"@crates_io__spider_fingerprint-2.38.1//:spider_fingerprint\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_fingerprint\",\n actual = \"@crates_io__spider_fingerprint-2.38.1//:spider_fingerprint\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_scraper-0.1.2\",\n actual = \"@crates_io__spider_scraper-0.1.2//:scraper\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_scraper\",\n actual = \"@crates_io__spider_scraper-0.1.2//:scraper\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_transformations-2.37.115\",\n actual = \"@crates_io__spider_transformations-2.37.115//:spider_transformations\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_transformations\",\n actual = \"@crates_io__spider_transformations-2.37.115//:spider_transformations\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_utils-2.47.18\",\n actual = \"@crates_io__spider_utils-2.47.18//:spider_utils\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"spider_utils\",\n actual = \"@crates_io__spider_utils-2.47.18//:spider_utils\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sqlx-0.8.6\",\n actual = \"@crates_io__sqlx-0.8.6//:sqlx\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sqlx\",\n actual = \"@crates_io__sqlx-0.8.6//:sqlx\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"statrs-0.18.0\",\n actual = \"@crates_io__statrs-0.18.0//:statrs\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"statrs\",\n actual = \"@crates_io__statrs-0.18.0//:statrs\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"string-interner-0.19.0\",\n actual = \"@crates_io__string-interner-0.19.0//:string_interner\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"string-interner\",\n actual = \"@crates_io__string-interner-0.19.0//:string_interner\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"string_concat-0.0.1\",\n actual = \"@crates_io__string_concat-0.0.1//:string_concat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"string_concat\",\n actual = \"@crates_io__string_concat-0.0.1//:string_concat\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum-0.27.2\",\n actual = \"@crates_io__strum-0.27.2//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"strum\",\n actual = \"@crates_io__strum-0.27.2//:strum\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sxd-document-0.3.2\",\n actual = \"@crates_io__sxd-document-0.3.2//:sxd_document\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sxd-document\",\n actual = \"@crates_io__sxd-document-0.3.2//:sxd_document\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sxd-xpath-0.4.2\",\n actual = \"@crates_io__sxd-xpath-0.4.2//:sxd_xpath\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sxd-xpath\",\n actual = \"@crates_io__sxd-xpath-0.4.2//:sxd_xpath\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sysinfo-0.38.4\",\n actual = \"@crates_io__sysinfo-0.38.4//:sysinfo\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sysinfo\",\n actual = \"@crates_io__sysinfo-0.38.4//:sysinfo\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.18\",\n actual = \"@crates_io__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@crates_io__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-1.50.0\",\n actual = \"@crates_io__tokio-1.50.0//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio\",\n actual = \"@crates_io__tokio-1.50.0//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-stream-0.1.18\",\n actual = \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-stream\",\n actual = \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tower-0.5.3\",\n actual = \"@crates_io__tower-0.5.3//:tower\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tower\",\n actual = \"@crates_io__tower-0.5.3//:tower\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ua_generator-0.5.52\",\n actual = \"@crates_io__ua_generator-0.5.52//:ua_generator\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ua_generator\",\n actual = \"@crates_io__ua_generator-0.5.52//:ua_generator\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"unicode-normalization-0.1.25\",\n actual = \"@crates_io__unicode-normalization-0.1.25//:unicode_normalization\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"unicode-normalization\",\n actual = \"@crates_io__unicode-normalization-0.1.25//:unicode_normalization\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"url-2.5.8\",\n actual = \"@crates_io__url-2.5.8//:url\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"url\",\n actual = \"@crates_io__url-2.5.8//:url\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"warp-0.3.7\",\n actual = \"@crates_io__warp-0.3.7//:warp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"warp\",\n actual = \"@crates_io__warp-0.3.7//:warp\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"which-6.0.3\",\n actual = \"@crates_io__which-6.0.3//:which\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"which\",\n actual = \"@crates_io__which-6.0.3//:which\",\n tags = [\"manual\"],\n)\n", + "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"benches\": {\n _COMMON_CONDITION: {\n \"criterion\": Label(\"@crates_io//:criterion-0.8.2\"),\n },\n },\n \"spider\": {\n _COMMON_CONDITION: {\n \"ahash\": Label(\"@crates_io//:ahash-0.8.12\"),\n \"aho-corasick\": Label(\"@crates_io//:aho-corasick-1.1.4\"),\n \"auto_encoder\": Label(\"@crates_io//:auto_encoder-0.1.10\"),\n \"base64\": Label(\"@crates_io//:base64-0.22.1\"),\n \"bytes\": Label(\"@crates_io//:bytes-1.11.1\"),\n \"case_insensitive_string\": Label(\"@crates_io//:case_insensitive_string-0.2.10\"),\n \"chromey\": Label(\"@crates_io//:chromey-2.40.0\"),\n \"cookie\": Label(\"@crates_io//:cookie-0.18.1\"),\n \"fastrand\": Label(\"@crates_io//:fastrand-2.3.0\"),\n \"flexbuffers\": Label(\"@crates_io//:flexbuffers-25.12.19\"),\n \"h2\": Label(\"@crates_io//:h2-0.4.13\"),\n \"hashbrown\": Label(\"@crates_io//:hashbrown-0.16.1\"),\n \"home\": Label(\"@crates_io//:home-0.5.12\"),\n \"lazy_static\": Label(\"@crates_io//:lazy_static-1.5.0\"),\n \"libc\": Label(\"@crates_io//:libc-0.2.183\"),\n \"log\": Label(\"@crates_io//:log-0.4.29\"),\n \"lol_html\": Label(\"@crates_io//:lol_html-2.7.2\"),\n \"num_cpus\": Label(\"@crates_io//:num_cpus-1.17.0\"),\n \"percent-encoding\": Label(\"@crates_io//:percent-encoding-2.3.2\"),\n \"phf\": Label(\"@crates_io//:phf-0.11.3\"),\n \"pin-project-lite\": Label(\"@crates_io//:pin-project-lite-0.2.17\"),\n \"quick-xml\": Label(\"@crates_io//:quick-xml-0.39.2\"),\n \"rand\": Label(\"@crates_io//:rand-0.9.2\"),\n \"regex\": Label(\"@crates_io//:regex-1.12.3\"),\n \"serde\": Label(\"@crates_io//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n \"serde_regex\": Label(\"@crates_io//:serde_regex-1.1.0\"),\n \"smallvec\": Label(\"@crates_io//:smallvec-1.15.1\"),\n \"spider_fingerprint\": Label(\"@crates_io//:spider_fingerprint-2.38.1\"),\n \"sqlx\": Label(\"@crates_io//:sqlx-0.8.6\"),\n \"statrs\": Label(\"@crates_io//:statrs-0.18.0\"),\n \"string-interner\": Label(\"@crates_io//:string-interner-0.19.0\"),\n \"string_concat\": Label(\"@crates_io//:string_concat-0.0.1\"),\n \"strum\": Label(\"@crates_io//:strum-0.27.2\"),\n \"sysinfo\": Label(\"@crates_io//:sysinfo-0.38.4\"),\n \"tokio-stream\": Label(\"@crates_io//:tokio-stream-0.1.18\"),\n \"tower\": Label(\"@crates_io//:tower-0.5.3\"),\n \"ua_generator\": Label(\"@crates_io//:ua_generator-0.5.52\"),\n \"url\": Label(\"@crates_io//:url-2.5.8\"),\n \"which\": Label(\"@crates_io//:which-6.0.3\"),\n },\n \"aarch64-unknown-linux-gnu\": {\n \"io-uring\": Label(\"@crates_io//:io-uring-0.7.11\"),\n },\n \"cfg(not(target_arch = \\\"wasm32\\\"))\": {\n \"reqwest\": Label(\"@crates_io//:reqwest-0.13.2\"),\n \"tokio\": Label(\"@crates_io//:tokio-1.50.0\"),\n },\n \"cfg(target_arch = \\\"wasm32\\\")\": {\n \"reqwest\": Label(\"@crates_io//:reqwest-0.13.2\"),\n \"tokio\": Label(\"@crates_io//:tokio-1.50.0\"),\n },\n \"x86_64-unknown-linux-gnu\": {\n \"io-uring\": Label(\"@crates_io//:io-uring-0.7.11\"),\n },\n \"x86_64-unknown-nixos-gnu\": {\n \"io-uring\": Label(\"@crates_io//:io-uring-0.7.11\"),\n },\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n \"aho-corasick\": Label(\"@crates_io//:aho-corasick-1.1.4\"),\n \"base64\": Label(\"@crates_io//:base64-0.22.1\"),\n \"dashmap\": Label(\"@crates_io//:dashmap-6.1.0\"),\n \"llm_models_spider\": Label(\"@crates_io//:llm_models_spider-0.1.42\"),\n \"log\": Label(\"@crates_io//:log-0.4.29\"),\n \"lol_html\": Label(\"@crates_io//:lol_html-2.7.2\"),\n \"parking_lot\": Label(\"@crates_io//:parking_lot-0.12.5\"),\n \"reqwest\": Label(\"@crates_io//:reqwest-0.13.2\"),\n \"serde\": Label(\"@crates_io//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n \"thiserror\": Label(\"@crates_io//:thiserror-2.0.18\"),\n \"tokio\": Label(\"@crates_io//:tokio-1.50.0\"),\n },\n },\n \"spider_agent_html\": {\n _COMMON_CONDITION: {\n \"aho-corasick\": Label(\"@crates_io//:aho-corasick-1.1.4\"),\n \"lol_html\": Label(\"@crates_io//:lol_html-2.7.2\"),\n \"serde\": Label(\"@crates_io//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n },\n },\n \"spider_agent_types\": {\n _COMMON_CONDITION: {\n \"aho-corasick\": Label(\"@crates_io//:aho-corasick-1.1.4\"),\n \"llm_models_spider\": Label(\"@crates_io//:llm_models_spider-0.1.42\"),\n \"serde\": Label(\"@crates_io//:serde-1.0.228\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n },\n },\n \"spider_cli\": {\n _COMMON_CONDITION: {\n \"clap\": Label(\"@crates_io//:clap-4.6.0\"),\n \"env_logger\": Label(\"@crates_io//:env_logger-0.11.9\"),\n \"percent-encoding\": Label(\"@crates_io//:percent-encoding-2.3.2\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n \"tokio\": Label(\"@crates_io//:tokio-1.50.0\"),\n \"unicode-normalization\": Label(\"@crates_io//:unicode-normalization-0.1.25\"),\n },\n },\n \"examples\": {\n _COMMON_CONDITION: {\n \"spider_transformations\": Label(\"@crates_io//:spider_transformations-2.37.115\"),\n \"spider_utils\": Label(\"@crates_io//:spider_utils-2.47.18\"),\n },\n },\n \"spider_utils\": {\n _COMMON_CONDITION: {\n \"hashbrown\": Label(\"@crates_io//:hashbrown-0.16.1\"),\n \"lazy_static\": Label(\"@crates_io//:lazy_static-1.5.0\"),\n \"log\": Label(\"@crates_io//:log-0.4.29\"),\n \"spider_scraper\": Label(\"@crates_io//:spider_scraper-0.1.2\"),\n \"sxd-document\": Label(\"@crates_io//:sxd-document-0.3.2\"),\n \"sxd-xpath\": Label(\"@crates_io//:sxd-xpath-0.4.2\"),\n \"tokio-stream\": Label(\"@crates_io//:tokio-stream-0.1.18\"),\n },\n },\n \"spider_worker\": {\n _COMMON_CONDITION: {\n \"env_logger\": Label(\"@crates_io//:env_logger-0.11.9\"),\n \"lazy_static\": Label(\"@crates_io//:lazy_static-1.5.0\"),\n \"warp\": Label(\"@crates_io//:warp-0.3.7\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"benches\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider\": {\n _COMMON_CONDITION: {\n },\n \"aarch64-unknown-linux-gnu\": {\n },\n \"cfg(not(target_arch = \\\"wasm32\\\"))\": {\n },\n \"cfg(target_arch = \\\"wasm32\\\")\": {\n },\n \"x86_64-unknown-linux-gnu\": {\n },\n \"x86_64-unknown-nixos-gnu\": {\n },\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent_html\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent_types\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_cli\": {\n _COMMON_CONDITION: {\n },\n },\n \"examples\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_utils\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_worker\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"benches\": {\n },\n \"spider\": {\n _COMMON_CONDITION: {\n \"env_logger\": Label(\"@crates_io//:env_logger-0.11.9\"),\n },\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n \"dotenvy\": Label(\"@crates_io//:dotenvy-0.15.7\"),\n \"env_logger\": Label(\"@crates_io//:env_logger-0.11.9\"),\n \"fastrand\": Label(\"@crates_io//:fastrand-2.3.0\"),\n \"futures\": Label(\"@crates_io//:futures-0.3.32\"),\n },\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n _COMMON_CONDITION: {\n \"console-subscriber\": Label(\"@crates_io//:console-subscriber-0.4.1\"),\n \"convert_case\": Label(\"@crates_io//:convert_case-0.6.0\"),\n \"env_logger\": Label(\"@crates_io//:env_logger-0.11.9\"),\n \"flexbuffers\": Label(\"@crates_io//:flexbuffers-25.12.19\"),\n \"htr\": Label(\"@crates_io//:htr-0.5.27\"),\n \"serde_json\": Label(\"@crates_io//:serde_json-1.0.149\"),\n },\n },\n \"spider_utils\": {\n _COMMON_CONDITION: {\n \"tokio\": Label(\"@crates_io//:tokio-1.50.0\"),\n },\n },\n \"spider_worker\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"benches\": {\n },\n \"spider\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_utils\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_worker\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n \"async-trait\": Label(\"@crates_io//:async-trait-0.1.89\"),\n },\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"benches\": {\n },\n \"spider\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_utils\": {\n _COMMON_CONDITION: {\n },\n },\n \"spider_worker\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"benches\": {\n },\n \"spider\": {\n },\n \"spider_agent\": {\n },\n \"spider_agent_html\": {\n },\n \"spider_agent_types\": {\n },\n \"spider_cli\": {\n },\n \"examples\": {\n },\n \"spider_utils\": {\n },\n \"spider_worker\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-linux-android\": [],\n \"aarch64-pc-windows-gnullvm\": [],\n \"aarch64-pc-windows-msvc\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"aarch64-uwp-windows-msvc\": [],\n \"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_os = \\\"windows\\\"))\": [],\n \"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_vendor = \\\"apple\\\", any(target_os = \\\"ios\\\", target_os = \\\"macos\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(any(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), all(target_arch = \\\"arm\\\", target_endian = \\\"little\\\")), any(target_os = \\\"android\\\", target_os = \\\"linux\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_os = \\\"linux\\\"))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"loongarch64\\\", target_os = \\\"linux\\\"))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\": [],\n \"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p3\\\"))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [],\n \"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"cfg(all(target_os = \\\"redox\\\", not(target_arch = \\\"wasm32\\\")))\": [],\n \"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\": [],\n \"cfg(all(unix, not(target_os = \\\"android\\\"), not(target_vendor = \\\"apple\\\"), not(target_arch = \\\"wasm32\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(all(unix, not(target_os = \\\"macos\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any())\": [],\n \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\": [],\n \"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\": [],\n \"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\": [],\n \"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\", target_os = \\\"redox\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(windows, unix))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(any(windows, unix, target_os = \\\"redox\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(all(target_arch = \\\"arm\\\", target_os = \\\"none\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(any(target_os = \\\"unknown\\\", target_arch = \\\"wasm32\\\")))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(any(target_os = \\\"windows\\\", target_vendor = \\\"apple\\\")))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_arch = \\\"wasm32\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(target_has_atomic = \\\"ptr\\\"))\": [],\n \"cfg(not(windows))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(not(windows_raw_dylib))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\",\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(target_arch = \\\"spirv\\\")\": [],\n \"cfg(target_arch = \\\"wasm32\\\")\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\",\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(target_feature = \\\"atomics\\\")\": [],\n \"cfg(target_os = \\\"android\\\")\": [],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"linux\\\")\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(target_os = \\\"macos\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(target_os = \\\"netbsd\\\")\": [],\n \"cfg(target_os = \\\"redox\\\")\": [],\n \"cfg(target_os = \\\"solaris\\\")\": [],\n \"cfg(target_os = \\\"vxworks\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"cfg(target_os = \\\"windows\\\")\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(target_vendor = \\\"apple\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"cfg(windows)\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"cfg(windows_raw_dylib)\": [],\n \"i686-pc-windows-gnu\": [],\n \"i686-pc-windows-gnullvm\": [],\n \"i686-pc-windows-msvc\": [],\n \"i686-uwp-windows-gnu\": [],\n \"i686-uwp-windows-msvc\": [],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-gnu\": [],\n \"x86_64-pc-windows-gnullvm\": [],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n \"x86_64-uwp-windows-gnu\": [],\n \"x86_64-uwp-windows-msvc\": [],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"crates_io__adler2-2.0.1\",\n sha256 = \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/adler2/2.0.1/download\"],\n strip_prefix = \"adler2-2.0.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.adler2-2.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ahash-0.8.12\",\n sha256 = \"5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ahash/0.8.12/download\"],\n strip_prefix = \"ahash-0.8.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ahash-0.8.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__aho-corasick-1.1.4\",\n sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n strip_prefix = \"aho-corasick-1.1.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.aho-corasick-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__alloc-no-stdlib-2.0.4\",\n sha256 = \"cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/alloc-no-stdlib/2.0.4/download\"],\n strip_prefix = \"alloc-no-stdlib-2.0.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.alloc-no-stdlib-2.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__alloc-stdlib-0.2.2\",\n sha256 = \"94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/alloc-stdlib/0.2.2/download\"],\n strip_prefix = \"alloc-stdlib-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.alloc-stdlib-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__alloca-0.4.0\",\n sha256 = \"e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/alloca/0.4.0/download\"],\n strip_prefix = \"alloca-0.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.alloca-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__allocator-api2-0.2.21\",\n sha256 = \"683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/allocator-api2/0.2.21/download\"],\n strip_prefix = \"allocator-api2-0.2.21\",\n build_file = Label(\"@crates_io//crates_io:BUILD.allocator-api2-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anes-0.1.6\",\n sha256 = \"4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anes/0.1.6/download\"],\n strip_prefix = \"anes-0.1.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anes-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstream-0.6.21\",\n sha256 = \"43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/0.6.21/download\"],\n strip_prefix = \"anstream-0.6.21\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstream-0.6.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstream-1.0.0\",\n sha256 = \"824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/1.0.0/download\"],\n strip_prefix = \"anstream-1.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstream-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstyle-1.0.14\",\n sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n strip_prefix = \"anstyle-1.0.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstyle-1.0.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstyle-parse-0.2.7\",\n sha256 = \"4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/0.2.7/download\"],\n strip_prefix = \"anstyle-parse-0.2.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstyle-parse-0.2.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstyle-parse-1.0.0\",\n sha256 = \"52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/1.0.0/download\"],\n strip_prefix = \"anstyle-parse-1.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstyle-parse-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__anyhow-1.0.102\",\n sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n strip_prefix = \"anyhow-1.0.102\",\n build_file = Label(\"@crates_io//crates_io:BUILD.anyhow-1.0.102.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__approx-0.5.1\",\n sha256 = \"cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/approx/0.5.1/download\"],\n strip_prefix = \"approx-0.5.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.approx-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__async-compression-0.4.41\",\n sha256 = \"d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/async-compression/0.4.41/download\"],\n strip_prefix = \"async-compression-0.4.41\",\n build_file = Label(\"@crates_io//crates_io:BUILD.async-compression-0.4.41.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__async-stream-0.3.6\",\n sha256 = \"0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/async-stream/0.3.6/download\"],\n strip_prefix = \"async-stream-0.3.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.async-stream-0.3.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__async-stream-impl-0.3.6\",\n sha256 = \"c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/async-stream-impl/0.3.6/download\"],\n strip_prefix = \"async-stream-impl-0.3.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.async-stream-impl-0.3.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__async-trait-0.1.89\",\n sha256 = \"9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/async-trait/0.1.89/download\"],\n strip_prefix = \"async-trait-0.1.89\",\n build_file = Label(\"@crates_io//crates_io:BUILD.async-trait-0.1.89.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__atoi-2.0.0\",\n sha256 = \"f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/atoi/2.0.0/download\"],\n strip_prefix = \"atoi-2.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.atoi-2.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__atomic-waker-1.1.2\",\n sha256 = \"1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/atomic-waker/1.1.2/download\"],\n strip_prefix = \"atomic-waker-1.1.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.atomic-waker-1.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__auto_encoder-0.1.10\",\n sha256 = \"3f6364e11e0270035ec392151a54f1476e6b3612ef9f4fe09d35e72a8cebcb65\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/auto_encoder/0.1.10/download\"],\n strip_prefix = \"auto_encoder-0.1.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.auto_encoder-0.1.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__aws-lc-rs-1.16.1\",\n sha256 = \"94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aws-lc-rs/1.16.1/download\"],\n strip_prefix = \"aws-lc-rs-1.16.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.aws-lc-rs-1.16.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__aws-lc-sys-0.38.0\",\n sha256 = \"4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aws-lc-sys/0.38.0/download\"],\n strip_prefix = \"aws-lc-sys-0.38.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.aws-lc-sys-0.38.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__axum-0.7.9\",\n sha256 = \"edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/axum/0.7.9/download\"],\n strip_prefix = \"axum-0.7.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.axum-0.7.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__axum-core-0.4.5\",\n sha256 = \"09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/axum-core/0.4.5/download\"],\n strip_prefix = \"axum-core-0.4.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.axum-core-0.4.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__base64-0.21.7\",\n sha256 = \"9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64/0.21.7/download\"],\n strip_prefix = \"base64-0.21.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.base64-0.21.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__base64-0.22.1\",\n sha256 = \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64/0.22.1/download\"],\n strip_prefix = \"base64-0.22.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.base64-0.22.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__base64ct-1.8.3\",\n sha256 = \"2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base64ct/1.8.3/download\"],\n strip_prefix = \"base64ct-1.8.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.base64ct-1.8.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__bitflags-1.3.2\",\n sha256 = \"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/1.3.2/download\"],\n strip_prefix = \"bitflags-1.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.bitflags-1.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__bitflags-2.11.0\",\n sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n strip_prefix = \"bitflags-2.11.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.bitflags-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__block-buffer-0.10.4\",\n sha256 = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/block-buffer/0.10.4/download\"],\n strip_prefix = \"block-buffer-0.10.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.block-buffer-0.10.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__brotli-8.0.2\",\n sha256 = \"4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/brotli/8.0.2/download\"],\n strip_prefix = \"brotli-8.0.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.brotli-8.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__brotli-decompressor-4.0.3\",\n sha256 = \"a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/brotli-decompressor/4.0.3/download\"],\n strip_prefix = \"brotli-decompressor-4.0.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.brotli-decompressor-4.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__brotli-decompressor-5.0.0\",\n sha256 = \"874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/brotli-decompressor/5.0.0/download\"],\n strip_prefix = \"brotli-decompressor-5.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.brotli-decompressor-5.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__bumpalo-3.20.2\",\n sha256 = \"5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bumpalo/3.20.2/download\"],\n strip_prefix = \"bumpalo-3.20.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.bumpalo-3.20.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__bytemuck-1.25.0\",\n sha256 = \"c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytemuck/1.25.0/download\"],\n strip_prefix = \"bytemuck-1.25.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.bytemuck-1.25.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__byteorder-1.5.0\",\n sha256 = \"1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byteorder/1.5.0/download\"],\n strip_prefix = \"byteorder-1.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.byteorder-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__bytes-1.11.1\",\n sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n strip_prefix = \"bytes-1.11.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.bytes-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__case_insensitive_string-0.2.10\",\n sha256 = \"ca6260a8cc32da29c626ce456aad5c579c3a36294722f00cccceeb64968820f7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/case_insensitive_string/0.2.10/download\"],\n strip_prefix = \"case_insensitive_string-0.2.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.case_insensitive_string-0.2.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cast-0.3.0\",\n sha256 = \"37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cast/0.3.0/download\"],\n strip_prefix = \"cast-0.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cast-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__castaway-0.2.4\",\n sha256 = \"dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/castaway/0.2.4/download\"],\n strip_prefix = \"castaway-0.2.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.castaway-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cc-1.2.57\",\n sha256 = \"7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cc/1.2.57/download\"],\n strip_prefix = \"cc-1.2.57\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cc-1.2.57.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cesu8-1.1.0\",\n sha256 = \"6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cesu8/1.1.0/download\"],\n strip_prefix = \"cesu8-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cesu8-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cfg_aliases-0.2.1\",\n sha256 = \"613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg_aliases/0.2.1/download\"],\n strip_prefix = \"cfg_aliases-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cfg_aliases-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__chardetng-0.1.17\",\n sha256 = \"14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/chardetng/0.1.17/download\"],\n strip_prefix = \"chardetng-0.1.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.chardetng-0.1.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__chromey-2.40.0\",\n sha256 = \"183679faab96df2da020aa6e56eb9302cae67ce21cc00adbba60254c6d23a262\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/chromey/2.40.0/download\"],\n strip_prefix = \"chromey-2.40.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.chromey-2.40.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ciborium-0.2.2\",\n sha256 = \"42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium/0.2.2/download\"],\n strip_prefix = \"ciborium-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ciborium-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ciborium-io-0.2.2\",\n sha256 = \"05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-io/0.2.2/download\"],\n strip_prefix = \"ciborium-io-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ciborium-io-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ciborium-ll-0.2.2\",\n sha256 = \"57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ciborium-ll/0.2.2/download\"],\n strip_prefix = \"ciborium-ll-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ciborium-ll-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__clap-4.6.0\",\n sha256 = \"b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.6.0/download\"],\n strip_prefix = \"clap-4.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.clap-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__clap_builder-4.6.0\",\n sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n strip_prefix = \"clap_builder-4.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.clap_builder-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__clap_derive-4.6.0\",\n sha256 = \"1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.6.0/download\"],\n strip_prefix = \"clap_derive-4.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.clap_derive-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__clap_lex-1.1.0\",\n sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n strip_prefix = \"clap_lex-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.clap_lex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cmake-0.1.57\",\n sha256 = \"75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cmake/0.1.57/download\"],\n strip_prefix = \"cmake-0.1.57\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cmake-0.1.57.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__colorchoice-1.0.5\",\n sha256 = \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.5/download\"],\n strip_prefix = \"colorchoice-1.0.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.colorchoice-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__combine-4.6.7\",\n sha256 = \"ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/combine/4.6.7/download\"],\n strip_prefix = \"combine-4.6.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.combine-4.6.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__compact_str-0.8.1\",\n sha256 = \"3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compact_str/0.8.1/download\"],\n strip_prefix = \"compact_str-0.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.compact_str-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__compression-codecs-0.4.37\",\n sha256 = \"eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compression-codecs/0.4.37/download\"],\n strip_prefix = \"compression-codecs-0.4.37\",\n build_file = Label(\"@crates_io//crates_io:BUILD.compression-codecs-0.4.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__compression-core-0.4.31\",\n sha256 = \"75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compression-core/0.4.31/download\"],\n strip_prefix = \"compression-core-0.4.31\",\n build_file = Label(\"@crates_io//crates_io:BUILD.compression-core-0.4.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__concurrent-queue-2.5.0\",\n sha256 = \"4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/concurrent-queue/2.5.0/download\"],\n strip_prefix = \"concurrent-queue-2.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.concurrent-queue-2.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__console-api-0.8.1\",\n sha256 = \"8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/console-api/0.8.1/download\"],\n strip_prefix = \"console-api-0.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.console-api-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__console-subscriber-0.4.1\",\n sha256 = \"6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/console-subscriber/0.4.1/download\"],\n strip_prefix = \"console-subscriber-0.4.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.console-subscriber-0.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__const-oid-0.9.6\",\n sha256 = \"c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const-oid/0.9.6/download\"],\n strip_prefix = \"const-oid-0.9.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.const-oid-0.9.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__convert_case-0.5.0\",\n sha256 = \"fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/convert_case/0.5.0/download\"],\n strip_prefix = \"convert_case-0.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.convert_case-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__convert_case-0.6.0\",\n sha256 = \"ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/convert_case/0.6.0/download\"],\n strip_prefix = \"convert_case-0.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.convert_case-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__convert_case-0.8.0\",\n sha256 = \"baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/convert_case/0.8.0/download\"],\n strip_prefix = \"convert_case-0.8.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.convert_case-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cookie-0.18.1\",\n sha256 = \"4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cookie/0.18.1/download\"],\n strip_prefix = \"cookie-0.18.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cookie-0.18.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cookie_store-0.22.1\",\n sha256 = \"15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cookie_store/0.22.1/download\"],\n strip_prefix = \"cookie_store-0.22.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cookie_store-0.22.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__core-foundation-0.10.1\",\n sha256 = \"b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/core-foundation/0.10.1/download\"],\n strip_prefix = \"core-foundation-0.10.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.core-foundation-0.10.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__core-foundation-0.9.4\",\n sha256 = \"91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/core-foundation/0.9.4/download\"],\n strip_prefix = \"core-foundation-0.9.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.core-foundation-0.9.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__core-foundation-sys-0.8.7\",\n sha256 = \"773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/core-foundation-sys/0.8.7/download\"],\n strip_prefix = \"core-foundation-sys-0.8.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.core-foundation-sys-0.8.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cpufeatures-0.2.17\",\n sha256 = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cpufeatures/0.2.17/download\"],\n strip_prefix = \"cpufeatures-0.2.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cpufeatures-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crc-3.4.0\",\n sha256 = \"5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crc/3.4.0/download\"],\n strip_prefix = \"crc-3.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crc-3.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crc-catalog-2.4.0\",\n sha256 = \"19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crc-catalog/2.4.0/download\"],\n strip_prefix = \"crc-catalog-2.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crc-catalog-2.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crc32fast-1.5.0\",\n sha256 = \"9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crc32fast/1.5.0/download\"],\n strip_prefix = \"crc32fast-1.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crc32fast-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__criterion-0.8.2\",\n sha256 = \"950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion/0.8.2/download\"],\n strip_prefix = \"criterion-0.8.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.criterion-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__criterion-plot-0.8.2\",\n sha256 = \"d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/criterion-plot/0.8.2/download\"],\n strip_prefix = \"criterion-plot-0.8.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.criterion-plot-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crossbeam-channel-0.5.15\",\n sha256 = \"82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-channel/0.5.15/download\"],\n strip_prefix = \"crossbeam-channel-0.5.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crossbeam-channel-0.5.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crossbeam-deque-0.8.6\",\n sha256 = \"9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-deque/0.8.6/download\"],\n strip_prefix = \"crossbeam-deque-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crossbeam-deque-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crossbeam-epoch-0.9.18\",\n sha256 = \"5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-epoch/0.9.18/download\"],\n strip_prefix = \"crossbeam-epoch-0.9.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crossbeam-epoch-0.9.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crossbeam-queue-0.3.12\",\n sha256 = \"0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-queue/0.3.12/download\"],\n strip_prefix = \"crossbeam-queue-0.3.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crossbeam-queue-0.3.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crossbeam-utils-0.8.21\",\n sha256 = \"d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crossbeam-utils/0.8.21/download\"],\n strip_prefix = \"crossbeam-utils-0.8.21\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crossbeam-utils-0.8.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crunchy-0.2.4\",\n sha256 = \"460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crunchy/0.2.4/download\"],\n strip_prefix = \"crunchy-0.2.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crunchy-0.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__crypto-common-0.1.7\",\n sha256 = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-common/0.1.7/download\"],\n strip_prefix = \"crypto-common-0.1.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.crypto-common-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cssparser-0.31.2\",\n sha256 = \"5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cssparser/0.31.2/download\"],\n strip_prefix = \"cssparser-0.31.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cssparser-0.31.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cssparser-0.36.0\",\n sha256 = \"dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cssparser/0.36.0/download\"],\n strip_prefix = \"cssparser-0.36.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cssparser-0.36.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__cssparser-macros-0.6.1\",\n sha256 = \"13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cssparser-macros/0.6.1/download\"],\n strip_prefix = \"cssparser-macros-0.6.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.cssparser-macros-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__dashmap-6.1.0\",\n sha256 = \"5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dashmap/6.1.0/download\"],\n strip_prefix = \"dashmap-6.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.dashmap-6.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__data-encoding-2.10.0\",\n sha256 = \"d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/data-encoding/2.10.0/download\"],\n strip_prefix = \"data-encoding-2.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.data-encoding-2.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__der-0.7.10\",\n sha256 = \"e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/der/0.7.10/download\"],\n strip_prefix = \"der-0.7.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.der-0.7.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__deranged-0.5.8\",\n sha256 = \"7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/deranged/0.5.8/download\"],\n strip_prefix = \"deranged-0.5.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.deranged-0.5.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__derive_more-0.99.20\",\n sha256 = \"6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/derive_more/0.99.20/download\"],\n strip_prefix = \"derive_more-0.99.20\",\n build_file = Label(\"@crates_io//crates_io:BUILD.derive_more-0.99.20.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__derive_more-2.1.1\",\n sha256 = \"d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/derive_more/2.1.1/download\"],\n strip_prefix = \"derive_more-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.derive_more-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__derive_more-impl-2.1.1\",\n sha256 = \"799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/derive_more-impl/2.1.1/download\"],\n strip_prefix = \"derive_more-impl-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.derive_more-impl-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__digest-0.10.7\",\n sha256 = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/digest/0.10.7/download\"],\n strip_prefix = \"digest-0.10.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.digest-0.10.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__displaydoc-0.2.5\",\n sha256 = \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/displaydoc/0.2.5/download\"],\n strip_prefix = \"displaydoc-0.2.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.displaydoc-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__document-features-0.2.12\",\n sha256 = \"d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/document-features/0.2.12/download\"],\n strip_prefix = \"document-features-0.2.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.document-features-0.2.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__dotenvy-0.15.7\",\n sha256 = \"1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dotenvy/0.15.7/download\"],\n strip_prefix = \"dotenvy-0.15.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.dotenvy-0.15.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__dtoa-1.0.11\",\n sha256 = \"4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dtoa/1.0.11/download\"],\n strip_prefix = \"dtoa-1.0.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.dtoa-1.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__dtoa-short-0.3.5\",\n sha256 = \"cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dtoa-short/0.3.5/download\"],\n strip_prefix = \"dtoa-short-0.3.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.dtoa-short-0.3.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__dunce-1.0.5\",\n sha256 = \"92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/dunce/1.0.5/download\"],\n strip_prefix = \"dunce-1.0.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.dunce-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ego-tree-0.10.0\",\n sha256 = \"b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ego-tree/0.10.0/download\"],\n strip_prefix = \"ego-tree-0.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ego-tree-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__encoding_rs-0.8.35\",\n sha256 = \"75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/encoding_rs/0.8.35/download\"],\n strip_prefix = \"encoding_rs-0.8.35\",\n build_file = Label(\"@crates_io//crates_io:BUILD.encoding_rs-0.8.35.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__env_filter-1.0.0\",\n sha256 = \"7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/env_filter/1.0.0/download\"],\n strip_prefix = \"env_filter-1.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.env_filter-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__env_logger-0.11.9\",\n sha256 = \"b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/env_logger/0.11.9/download\"],\n strip_prefix = \"env_logger-0.11.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.env_logger-0.11.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__etcetera-0.8.0\",\n sha256 = \"136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/etcetera/0.8.0/download\"],\n strip_prefix = \"etcetera-0.8.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.etcetera-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__event-listener-5.4.1\",\n sha256 = \"e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/event-listener/5.4.1/download\"],\n strip_prefix = \"event-listener-5.4.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.event-listener-5.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__failure_derive-0.1.8\",\n sha256 = \"aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/failure_derive/0.1.8/download\"],\n strip_prefix = \"failure_derive-0.1.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.failure_derive-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fast_html2md-0.0.58\",\n sha256 = \"af3a0122fee1bcf6bb9f3d73782e911cce69d95b76a5e29e930af92cd4a8e4e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fast_html2md/0.0.58/download\"],\n strip_prefix = \"fast_html2md-0.0.58\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fast_html2md-0.0.58.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fast_html5ever-0.26.6\",\n sha256 = \"edb09004fadff4b73e882edeb42527d3789009ca821467169168867eeb71681d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fast_html5ever/0.26.6/download\"],\n strip_prefix = \"fast_html5ever-0.26.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fast_html5ever-0.26.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fast_markup5ever-0.11.1\",\n sha256 = \"f899f3b7e9bf005393dd90318d7e721e0ecd230073b675a6737ed502ecff557c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fast_markup5ever/0.11.1/download\"],\n strip_prefix = \"fast_markup5ever-0.11.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fast_markup5ever-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fastrand-2.3.0\",\n sha256 = \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fastrand/2.3.0/download\"],\n strip_prefix = \"fastrand-2.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fastrand-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__find-msvc-tools-0.1.9\",\n sha256 = \"5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/find-msvc-tools/0.1.9/download\"],\n strip_prefix = \"find-msvc-tools-0.1.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.find-msvc-tools-0.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__flate2-1.1.9\",\n sha256 = \"843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/flate2/1.1.9/download\"],\n strip_prefix = \"flate2-1.1.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.flate2-1.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__flexbuffers-25.12.19\",\n sha256 = \"8bc752b3d049e0705749b9999d0b130d6cf62935bc7762fd3bdb7636047abe43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/flexbuffers/25.12.19/download\"],\n strip_prefix = \"flexbuffers-25.12.19\",\n build_file = Label(\"@crates_io//crates_io:BUILD.flexbuffers-25.12.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__flume-0.11.1\",\n sha256 = \"da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/flume/0.11.1/download\"],\n strip_prefix = \"flume-0.11.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.flume-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fnv-1.0.7\",\n sha256 = \"3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fnv/1.0.7/download\"],\n strip_prefix = \"fnv-1.0.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fnv-1.0.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__foldhash-0.2.0\",\n sha256 = \"77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.2.0/download\"],\n strip_prefix = \"foldhash-0.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.foldhash-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__foreign-types-0.3.2\",\n sha256 = \"f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foreign-types/0.3.2/download\"],\n strip_prefix = \"foreign-types-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.foreign-types-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__foreign-types-shared-0.1.1\",\n sha256 = \"00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foreign-types-shared/0.1.1/download\"],\n strip_prefix = \"foreign-types-shared-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.foreign-types-shared-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__form_urlencoded-1.2.2\",\n sha256 = \"cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/form_urlencoded/1.2.2/download\"],\n strip_prefix = \"form_urlencoded-1.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.form_urlencoded-1.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fs_extra-1.3.0\",\n sha256 = \"42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fs_extra/1.3.0/download\"],\n strip_prefix = \"fs_extra-1.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fs_extra-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futf-0.1.5\",\n sha256 = \"df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futf/0.1.5/download\"],\n strip_prefix = \"futf-0.1.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futf-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-0.3.32\",\n sha256 = \"8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures/0.3.32/download\"],\n strip_prefix = \"futures-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-channel-0.3.32\",\n sha256 = \"07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-channel/0.3.32/download\"],\n strip_prefix = \"futures-channel-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-channel-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-core-0.3.32\",\n sha256 = \"7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-core/0.3.32/download\"],\n strip_prefix = \"futures-core-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-core-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-executor-0.3.32\",\n sha256 = \"baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-executor/0.3.32/download\"],\n strip_prefix = \"futures-executor-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-executor-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-intrusive-0.5.0\",\n sha256 = \"1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-intrusive/0.5.0/download\"],\n strip_prefix = \"futures-intrusive-0.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-intrusive-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-io-0.3.32\",\n sha256 = \"cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-io/0.3.32/download\"],\n strip_prefix = \"futures-io-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-io-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-macro-0.3.32\",\n sha256 = \"e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-macro/0.3.32/download\"],\n strip_prefix = \"futures-macro-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-macro-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-sink-0.3.32\",\n sha256 = \"c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-sink/0.3.32/download\"],\n strip_prefix = \"futures-sink-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-sink-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-task-0.3.32\",\n sha256 = \"037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-task/0.3.32/download\"],\n strip_prefix = \"futures-task-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-task-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-timer-3.0.3\",\n sha256 = \"f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-timer/3.0.3/download\"],\n strip_prefix = \"futures-timer-3.0.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-timer-3.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__futures-util-0.3.32\",\n sha256 = \"389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-util/0.3.32/download\"],\n strip_prefix = \"futures-util-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.futures-util-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__fxhash-0.2.1\",\n sha256 = \"c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/fxhash/0.2.1/download\"],\n strip_prefix = \"fxhash-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.fxhash-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__generic-array-0.14.7\",\n sha256 = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/generic-array/0.14.7/download\"],\n strip_prefix = \"generic-array-0.14.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.generic-array-0.14.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__getrandom-0.2.17\",\n sha256 = \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.2.17/download\"],\n strip_prefix = \"getrandom-0.2.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.getrandom-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__getrandom-0.3.4\",\n sha256 = \"899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.3.4/download\"],\n strip_prefix = \"getrandom-0.3.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.getrandom-0.3.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__getrandom-0.4.2\",\n sha256 = \"0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/getrandom/0.4.2/download\"],\n strip_prefix = \"getrandom-0.4.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.getrandom-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__h2-0.3.27\",\n sha256 = \"0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/h2/0.3.27/download\"],\n strip_prefix = \"h2-0.3.27\",\n build_file = Label(\"@crates_io//crates_io:BUILD.h2-0.3.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__h2-0.4.13\",\n sha256 = \"2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/h2/0.4.13/download\"],\n strip_prefix = \"h2-0.4.13\",\n build_file = Label(\"@crates_io//crates_io:BUILD.h2-0.4.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__half-2.7.1\",\n sha256 = \"6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/half/2.7.1/download\"],\n strip_prefix = \"half-2.7.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.half-2.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hashbrown-0.12.3\",\n sha256 = \"8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.12.3/download\"],\n strip_prefix = \"hashbrown-0.12.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hashbrown-0.12.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hashbrown-0.14.5\",\n sha256 = \"e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.14.5/download\"],\n strip_prefix = \"hashbrown-0.14.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hashbrown-0.14.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hashbrown-0.16.1\",\n sha256 = \"841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.16.1/download\"],\n strip_prefix = \"hashbrown-0.16.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hashbrown-0.16.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hashlink-0.10.0\",\n sha256 = \"7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashlink/0.10.0/download\"],\n strip_prefix = \"hashlink-0.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hashlink-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hdrhistogram-7.5.4\",\n sha256 = \"765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hdrhistogram/7.5.4/download\"],\n strip_prefix = \"hdrhistogram-7.5.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hdrhistogram-7.5.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__headers-0.3.9\",\n sha256 = \"06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/headers/0.3.9/download\"],\n strip_prefix = \"headers-0.3.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.headers-0.3.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__headers-core-0.2.0\",\n sha256 = \"e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/headers-core/0.2.0/download\"],\n strip_prefix = \"headers-core-0.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.headers-core-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__heck-0.4.1\",\n sha256 = \"95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.4.1/download\"],\n strip_prefix = \"heck-0.4.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.heck-0.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hermit-abi-0.5.2\",\n sha256 = \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hermit-abi/0.5.2/download\"],\n strip_prefix = \"hermit-abi-0.5.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hermit-abi-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hkdf-0.12.4\",\n sha256 = \"7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hkdf/0.12.4/download\"],\n strip_prefix = \"hkdf-0.12.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hkdf-0.12.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hmac-0.12.1\",\n sha256 = \"6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hmac/0.12.1/download\"],\n strip_prefix = \"hmac-0.12.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hmac-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__home-0.5.12\",\n sha256 = \"cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/home/0.5.12/download\"],\n strip_prefix = \"home-0.5.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.home-0.5.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__html5ever-0.27.0\",\n sha256 = \"c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/html5ever/0.27.0/download\"],\n strip_prefix = \"html5ever-0.27.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.html5ever-0.27.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__htr-0.5.27\",\n sha256 = \"8b0e5190458ce24b51fd95e34be40d23b8ec15e1d6cef76a8f8f321744efefc8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/htr/0.5.27/download\"],\n strip_prefix = \"htr-0.5.27\",\n build_file = Label(\"@crates_io//crates_io:BUILD.htr-0.5.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__http-0.2.12\",\n sha256 = \"601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http/0.2.12/download\"],\n strip_prefix = \"http-0.2.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.http-0.2.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__http-1.4.0\",\n sha256 = \"e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http/1.4.0/download\"],\n strip_prefix = \"http-1.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.http-1.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__http-body-0.4.6\",\n sha256 = \"7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http-body/0.4.6/download\"],\n strip_prefix = \"http-body-0.4.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.http-body-0.4.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__http-body-1.0.1\",\n sha256 = \"1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http-body/1.0.1/download\"],\n strip_prefix = \"http-body-1.0.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.http-body-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__http-body-util-0.1.3\",\n sha256 = \"b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/http-body-util/0.1.3/download\"],\n strip_prefix = \"http-body-util-0.1.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.http-body-util-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__httparse-1.10.1\",\n sha256 = \"6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/httparse/1.10.1/download\"],\n strip_prefix = \"httparse-1.10.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.httparse-1.10.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__httpdate-1.0.3\",\n sha256 = \"df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/httpdate/1.0.3/download\"],\n strip_prefix = \"httpdate-1.0.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.httpdate-1.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__humantime-2.3.0\",\n sha256 = \"135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/humantime/2.3.0/download\"],\n strip_prefix = \"humantime-2.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.humantime-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hyper-0.14.32\",\n sha256 = \"41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper/0.14.32/download\"],\n strip_prefix = \"hyper-0.14.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hyper-0.14.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hyper-1.8.1\",\n sha256 = \"2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper/1.8.1/download\"],\n strip_prefix = \"hyper-1.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hyper-1.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hyper-rustls-0.27.7\",\n sha256 = \"e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-rustls/0.27.7/download\"],\n strip_prefix = \"hyper-rustls-0.27.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hyper-rustls-0.27.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hyper-timeout-0.5.2\",\n sha256 = \"2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-timeout/0.5.2/download\"],\n strip_prefix = \"hyper-timeout-0.5.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hyper-timeout-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__hyper-util-0.1.20\",\n sha256 = \"96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hyper-util/0.1.20/download\"],\n strip_prefix = \"hyper-util-0.1.20\",\n build_file = Label(\"@crates_io//crates_io:BUILD.hyper-util-0.1.20.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_collections-2.1.1\",\n sha256 = \"4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_collections/2.1.1/download\"],\n strip_prefix = \"icu_collections-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_collections-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_locale_core-2.1.1\",\n sha256 = \"edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_locale_core/2.1.1/download\"],\n strip_prefix = \"icu_locale_core-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_locale_core-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_normalizer-2.1.1\",\n sha256 = \"5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_normalizer/2.1.1/download\"],\n strip_prefix = \"icu_normalizer-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_normalizer-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_normalizer_data-2.1.1\",\n sha256 = \"7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_normalizer_data/2.1.1/download\"],\n strip_prefix = \"icu_normalizer_data-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_normalizer_data-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_properties-2.1.2\",\n sha256 = \"020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_properties/2.1.2/download\"],\n strip_prefix = \"icu_properties-2.1.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_properties-2.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_properties_data-2.1.2\",\n sha256 = \"616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_properties_data/2.1.2/download\"],\n strip_prefix = \"icu_properties_data-2.1.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_properties_data-2.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__icu_provider-2.1.1\",\n sha256 = \"85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/icu_provider/2.1.1/download\"],\n strip_prefix = \"icu_provider-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.icu_provider-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__id-arena-2.3.0\",\n sha256 = \"3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/id-arena/2.3.0/download\"],\n strip_prefix = \"id-arena-2.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.id-arena-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__idna-1.1.0\",\n sha256 = \"3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/idna/1.1.0/download\"],\n strip_prefix = \"idna-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.idna-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__idna_adapter-1.2.1\",\n sha256 = \"3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/idna_adapter/1.2.1/download\"],\n strip_prefix = \"idna_adapter-1.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.idna_adapter-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__indexmap-1.9.3\",\n sha256 = \"bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/1.9.3/download\"],\n strip_prefix = \"indexmap-1.9.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.indexmap-1.9.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__indexmap-2.13.0\",\n sha256 = \"7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.13.0/download\"],\n strip_prefix = \"indexmap-2.13.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.indexmap-2.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__io-uring-0.7.11\",\n sha256 = \"fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/io-uring/0.7.11/download\"],\n strip_prefix = \"io-uring-0.7.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.io-uring-0.7.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ipnet-2.12.0\",\n sha256 = \"d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ipnet/2.12.0/download\"],\n strip_prefix = \"ipnet-2.12.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ipnet-2.12.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__iri-string-0.7.10\",\n sha256 = \"c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/iri-string/0.7.10/download\"],\n strip_prefix = \"iri-string-0.7.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.iri-string-0.7.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__itertools-0.13.0\",\n sha256 = \"413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.13.0/download\"],\n strip_prefix = \"itertools-0.13.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.itertools-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__itertools-0.14.0\",\n sha256 = \"2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.14.0/download\"],\n strip_prefix = \"itertools-0.14.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.itertools-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__itoa-1.0.17\",\n sha256 = \"92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itoa/1.0.17/download\"],\n strip_prefix = \"itoa-1.0.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.itoa-1.0.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__jiff-0.2.23\",\n sha256 = \"1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jiff/0.2.23/download\"],\n strip_prefix = \"jiff-0.2.23\",\n build_file = Label(\"@crates_io//crates_io:BUILD.jiff-0.2.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__jiff-static-0.2.23\",\n sha256 = \"2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jiff-static/0.2.23/download\"],\n strip_prefix = \"jiff-static-0.2.23\",\n build_file = Label(\"@crates_io//crates_io:BUILD.jiff-static-0.2.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__jni-0.21.1\",\n sha256 = \"1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jni/0.21.1/download\"],\n strip_prefix = \"jni-0.21.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.jni-0.21.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__jni-sys-0.3.0\",\n sha256 = \"8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jni-sys/0.3.0/download\"],\n strip_prefix = \"jni-sys-0.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.jni-sys-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__jobserver-0.1.34\",\n sha256 = \"9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/jobserver/0.1.34/download\"],\n strip_prefix = \"jobserver-0.1.34\",\n build_file = Label(\"@crates_io//crates_io:BUILD.jobserver-0.1.34.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__js-sys-0.3.91\",\n sha256 = \"b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/js-sys/0.3.91/download\"],\n strip_prefix = \"js-sys-0.3.91\",\n build_file = Label(\"@crates_io//crates_io:BUILD.js-sys-0.3.91.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__lazy_static-1.5.0\",\n sha256 = \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lazy_static/1.5.0/download\"],\n strip_prefix = \"lazy_static-1.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.lazy_static-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__leb128fmt-0.1.0\",\n sha256 = \"09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/leb128fmt/0.1.0/download\"],\n strip_prefix = \"leb128fmt-0.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.leb128fmt-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__libc-0.2.183\",\n sha256 = \"b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.183/download\"],\n strip_prefix = \"libc-0.2.183\",\n build_file = Label(\"@crates_io//crates_io:BUILD.libc-0.2.183.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__libm-0.2.16\",\n sha256 = \"b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libm/0.2.16/download\"],\n strip_prefix = \"libm-0.2.16\",\n build_file = Label(\"@crates_io//crates_io:BUILD.libm-0.2.16.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__libredox-0.1.14\",\n sha256 = \"1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libredox/0.1.14/download\"],\n strip_prefix = \"libredox-0.1.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.libredox-0.1.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__libsqlite3-sys-0.30.1\",\n sha256 = \"2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libsqlite3-sys/0.30.1/download\"],\n strip_prefix = \"libsqlite3-sys-0.30.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.libsqlite3-sys-0.30.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__linux-raw-sys-0.12.1\",\n sha256 = \"32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.12.1/download\"],\n strip_prefix = \"linux-raw-sys-0.12.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.linux-raw-sys-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__linux-raw-sys-0.4.15\",\n sha256 = \"d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.4.15/download\"],\n strip_prefix = \"linux-raw-sys-0.4.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.linux-raw-sys-0.4.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__litemap-0.8.1\",\n sha256 = \"6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/litemap/0.8.1/download\"],\n strip_prefix = \"litemap-0.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.litemap-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__litrs-1.0.0\",\n sha256 = \"11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/litrs/1.0.0/download\"],\n strip_prefix = \"litrs-1.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.litrs-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__llm_models_spider-0.1.42\",\n sha256 = \"2dff3eaea579ea35091f7899d095d96a03ccae3e72ee5ad7b69b7b38b0fd7694\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/llm_models_spider/0.1.42/download\"],\n strip_prefix = \"llm_models_spider-0.1.42\",\n build_file = Label(\"@crates_io//crates_io:BUILD.llm_models_spider-0.1.42.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__llm_readability-0.0.13\",\n sha256 = \"2dd03f1473eaaf56fe7dccef40ad64626b98f4dba23af0ab7fcfc7d2d6b6845c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/llm_readability/0.0.13/download\"],\n strip_prefix = \"llm_readability-0.0.13\",\n build_file = Label(\"@crates_io//crates_io:BUILD.llm_readability-0.0.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__lock_api-0.4.14\",\n sha256 = \"224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lock_api/0.4.14/download\"],\n strip_prefix = \"lock_api-0.4.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.lock_api-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__log-0.4.29\",\n sha256 = \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.29/download\"],\n strip_prefix = \"log-0.4.29\",\n build_file = Label(\"@crates_io//crates_io:BUILD.log-0.4.29.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__lol_html-2.7.2\",\n sha256 = \"5ff94cb6aef6ee52afd2c69331e9109906d855e82bd241f3110dfdf6185899ab\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lol_html/2.7.2/download\"],\n strip_prefix = \"lol_html-2.7.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.lol_html-2.7.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__lru-slab-0.1.2\",\n sha256 = \"112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lru-slab/0.1.2/download\"],\n strip_prefix = \"lru-slab-0.1.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.lru-slab-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__mac-0.1.1\",\n sha256 = \"c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mac/0.1.1/download\"],\n strip_prefix = \"mac-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.mac-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__markup5ever-0.12.1\",\n sha256 = \"16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/markup5ever/0.12.1/download\"],\n strip_prefix = \"markup5ever-0.12.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.markup5ever-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__markup5ever-0.13.0\",\n sha256 = \"d581ff8be69d08a2efa23a959d81aa22b739073f749f067348bd4f4ba4b69195\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/markup5ever/0.13.0/download\"],\n strip_prefix = \"markup5ever-0.13.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.markup5ever-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__markup5ever_rcdom-0.3.0\",\n sha256 = \"edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/markup5ever_rcdom/0.3.0/download\"],\n strip_prefix = \"markup5ever_rcdom-0.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.markup5ever_rcdom-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__matchers-0.2.0\",\n sha256 = \"d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/matchers/0.2.0/download\"],\n strip_prefix = \"matchers-0.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.matchers-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__matchit-0.7.3\",\n sha256 = \"0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/matchit/0.7.3/download\"],\n strip_prefix = \"matchit-0.7.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.matchit-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__matrixmultiply-0.3.10\",\n sha256 = \"a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/matrixmultiply/0.3.10/download\"],\n strip_prefix = \"matrixmultiply-0.3.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.matrixmultiply-0.3.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__md-5-0.10.6\",\n sha256 = \"d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/md-5/0.10.6/download\"],\n strip_prefix = \"md-5-0.10.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.md-5-0.10.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__mime-0.3.17\",\n sha256 = \"6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mime/0.3.17/download\"],\n strip_prefix = \"mime-0.3.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.mime-0.3.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__mime_guess-2.0.5\",\n sha256 = \"f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mime_guess/2.0.5/download\"],\n strip_prefix = \"mime_guess-2.0.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.mime_guess-2.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__minimal-lexical-0.2.1\",\n sha256 = \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minimal-lexical/0.2.1/download\"],\n strip_prefix = \"minimal-lexical-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.minimal-lexical-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__miniz_oxide-0.8.9\",\n sha256 = \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/miniz_oxide/0.8.9/download\"],\n strip_prefix = \"miniz_oxide-0.8.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.miniz_oxide-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__mio-1.1.1\",\n sha256 = \"a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mio/1.1.1/download\"],\n strip_prefix = \"mio-1.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.mio-1.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__multer-2.1.0\",\n sha256 = \"01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/multer/2.1.0/download\"],\n strip_prefix = \"multer-2.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.multer-2.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__nalgebra-0.33.2\",\n sha256 = \"26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nalgebra/0.33.2/download\"],\n strip_prefix = \"nalgebra-0.33.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.nalgebra-0.33.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__native-tls-0.2.18\",\n sha256 = \"465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/native-tls/0.2.18/download\"],\n strip_prefix = \"native-tls-0.2.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.native-tls-0.2.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__new_debug_unreachable-1.0.6\",\n sha256 = \"650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/new_debug_unreachable/1.0.6/download\"],\n strip_prefix = \"new_debug_unreachable-1.0.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.new_debug_unreachable-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__nom-7.1.3\",\n sha256 = \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nom/7.1.3/download\"],\n strip_prefix = \"nom-7.1.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.nom-7.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ntapi-0.4.3\",\n sha256 = \"c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ntapi/0.4.3/download\"],\n strip_prefix = \"ntapi-0.4.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ntapi-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-bigint-0.4.6\",\n sha256 = \"a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-bigint/0.4.6/download\"],\n strip_prefix = \"num-bigint-0.4.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-bigint-0.4.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-bigint-dig-0.8.6\",\n sha256 = \"e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-bigint-dig/0.8.6/download\"],\n strip_prefix = \"num-bigint-dig-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-bigint-dig-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-complex-0.4.6\",\n sha256 = \"73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-complex/0.4.6/download\"],\n strip_prefix = \"num-complex-0.4.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-complex-0.4.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-conv-0.2.0\",\n sha256 = \"cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-conv/0.2.0/download\"],\n strip_prefix = \"num-conv-0.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-conv-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-integer-0.1.46\",\n sha256 = \"7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-integer/0.1.46/download\"],\n strip_prefix = \"num-integer-0.1.46\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-integer-0.1.46.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-iter-0.1.45\",\n sha256 = \"1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-iter/0.1.45/download\"],\n strip_prefix = \"num-iter-0.1.45\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-iter-0.1.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-rational-0.4.2\",\n sha256 = \"f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-rational/0.4.2/download\"],\n strip_prefix = \"num-rational-0.4.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-rational-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num-traits-0.2.19\",\n sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n strip_prefix = \"num-traits-0.2.19\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num-traits-0.2.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num_cpus-1.17.0\",\n sha256 = \"91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num_cpus/1.17.0/download\"],\n strip_prefix = \"num_cpus-1.17.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num_cpus-1.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num_enum-0.5.11\",\n sha256 = \"1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num_enum/0.5.11/download\"],\n strip_prefix = \"num_enum-0.5.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num_enum-0.5.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__num_enum_derive-0.5.11\",\n sha256 = \"dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num_enum_derive/0.5.11/download\"],\n strip_prefix = \"num_enum_derive-0.5.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.num_enum_derive-0.5.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__objc2-core-foundation-0.3.2\",\n sha256 = \"2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/objc2-core-foundation/0.3.2/download\"],\n strip_prefix = \"objc2-core-foundation-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.objc2-core-foundation-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__objc2-io-kit-0.3.2\",\n sha256 = \"33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/objc2-io-kit/0.3.2/download\"],\n strip_prefix = \"objc2-io-kit-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.objc2-io-kit-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__once_cell-1.21.4\",\n sha256 = \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell/1.21.4/download\"],\n strip_prefix = \"once_cell-1.21.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.once_cell-1.21.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__oorandom-11.1.5\",\n sha256 = \"d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/oorandom/11.1.5/download\"],\n strip_prefix = \"oorandom-11.1.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.oorandom-11.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__openssl-0.10.76\",\n sha256 = \"951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/openssl/0.10.76/download\"],\n strip_prefix = \"openssl-0.10.76\",\n build_file = Label(\"@crates_io//crates_io:BUILD.openssl-0.10.76.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__openssl-macros-0.1.1\",\n sha256 = \"a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/openssl-macros/0.1.1/download\"],\n strip_prefix = \"openssl-macros-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.openssl-macros-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__openssl-probe-0.2.1\",\n sha256 = \"7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/openssl-probe/0.2.1/download\"],\n strip_prefix = \"openssl-probe-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.openssl-probe-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__openssl-sys-0.9.112\",\n sha256 = \"57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/openssl-sys/0.9.112/download\"],\n strip_prefix = \"openssl-sys-0.9.112\",\n build_file = Label(\"@crates_io//crates_io:BUILD.openssl-sys-0.9.112.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__page_size-0.6.0\",\n sha256 = \"30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/page_size/0.6.0/download\"],\n strip_prefix = \"page_size-0.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.page_size-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__parking-2.2.1\",\n sha256 = \"f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking/2.2.1/download\"],\n strip_prefix = \"parking-2.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.parking-2.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__parking_lot-0.12.5\",\n sha256 = \"93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot/0.12.5/download\"],\n strip_prefix = \"parking_lot-0.12.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.parking_lot-0.12.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__parking_lot_core-0.9.12\",\n sha256 = \"2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot_core/0.9.12/download\"],\n strip_prefix = \"parking_lot_core-0.9.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.parking_lot_core-0.9.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__paste-1.0.15\",\n sha256 = \"57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/paste/1.0.15/download\"],\n strip_prefix = \"paste-1.0.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.paste-1.0.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pem-rfc7468-0.7.0\",\n sha256 = \"88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pem-rfc7468/0.7.0/download\"],\n strip_prefix = \"pem-rfc7468-0.7.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pem-rfc7468-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__percent-encoding-2.3.2\",\n sha256 = \"9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/percent-encoding/2.3.2/download\"],\n strip_prefix = \"percent-encoding-2.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.percent-encoding-2.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__peresil-0.3.0\",\n sha256 = \"f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/peresil/0.3.0/download\"],\n strip_prefix = \"peresil-0.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.peresil-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf-0.10.1\",\n sha256 = \"fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf/0.10.1/download\"],\n strip_prefix = \"phf-0.10.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf-0.10.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf-0.11.3\",\n sha256 = \"1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf/0.11.3/download\"],\n strip_prefix = \"phf-0.11.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf-0.11.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf-0.13.1\",\n sha256 = \"c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf/0.13.1/download\"],\n strip_prefix = \"phf-0.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_codegen-0.10.0\",\n sha256 = \"4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_codegen/0.10.0/download\"],\n strip_prefix = \"phf_codegen-0.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_codegen-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_codegen-0.11.3\",\n sha256 = \"aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_codegen/0.11.3/download\"],\n strip_prefix = \"phf_codegen-0.11.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_codegen-0.11.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_codegen-0.13.1\",\n sha256 = \"49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_codegen/0.13.1/download\"],\n strip_prefix = \"phf_codegen-0.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_codegen-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_generator-0.10.0\",\n sha256 = \"5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_generator/0.10.0/download\"],\n strip_prefix = \"phf_generator-0.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_generator-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_generator-0.11.3\",\n sha256 = \"3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_generator/0.11.3/download\"],\n strip_prefix = \"phf_generator-0.11.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_generator-0.11.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_generator-0.13.1\",\n sha256 = \"135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_generator/0.13.1/download\"],\n strip_prefix = \"phf_generator-0.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_generator-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_macros-0.11.3\",\n sha256 = \"f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_macros/0.11.3/download\"],\n strip_prefix = \"phf_macros-0.11.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_macros-0.11.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_macros-0.13.1\",\n sha256 = \"812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_macros/0.13.1/download\"],\n strip_prefix = \"phf_macros-0.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_macros-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_shared-0.10.0\",\n sha256 = \"b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_shared/0.10.0/download\"],\n strip_prefix = \"phf_shared-0.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_shared-0.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_shared-0.11.3\",\n sha256 = \"67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_shared/0.11.3/download\"],\n strip_prefix = \"phf_shared-0.11.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_shared-0.11.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__phf_shared-0.13.1\",\n sha256 = \"e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/phf_shared/0.13.1/download\"],\n strip_prefix = \"phf_shared-0.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.phf_shared-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pin-project-1.1.11\",\n sha256 = \"f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project/1.1.11/download\"],\n strip_prefix = \"pin-project-1.1.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pin-project-1.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pin-project-internal-1.1.11\",\n sha256 = \"d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-internal/1.1.11/download\"],\n strip_prefix = \"pin-project-internal-1.1.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pin-project-internal-1.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pin-project-lite-0.2.17\",\n sha256 = \"a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-lite/0.2.17/download\"],\n strip_prefix = \"pin-project-lite-0.2.17\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pin-project-lite-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pin-utils-0.1.0\",\n sha256 = \"8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-utils/0.1.0/download\"],\n strip_prefix = \"pin-utils-0.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pin-utils-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pkcs1-0.7.5\",\n sha256 = \"c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pkcs1/0.7.5/download\"],\n strip_prefix = \"pkcs1-0.7.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pkcs1-0.7.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pkcs8-0.10.2\",\n sha256 = \"f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pkcs8/0.10.2/download\"],\n strip_prefix = \"pkcs8-0.10.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pkcs8-0.10.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__pkg-config-0.3.32\",\n sha256 = \"7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pkg-config/0.3.32/download\"],\n strip_prefix = \"pkg-config-0.3.32\",\n build_file = Label(\"@crates_io//crates_io:BUILD.pkg-config-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__plain-0.2.3\",\n sha256 = \"b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plain/0.2.3/download\"],\n strip_prefix = \"plain-0.2.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.plain-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__plotters-0.3.7\",\n sha256 = \"5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters/0.3.7/download\"],\n strip_prefix = \"plotters-0.3.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.plotters-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__plotters-backend-0.3.7\",\n sha256 = \"df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-backend/0.3.7/download\"],\n strip_prefix = \"plotters-backend-0.3.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.plotters-backend-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__plotters-svg-0.3.7\",\n sha256 = \"51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/plotters-svg/0.3.7/download\"],\n strip_prefix = \"plotters-svg-0.3.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.plotters-svg-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__portable-atomic-1.13.1\",\n sha256 = \"c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic/1.13.1/download\"],\n strip_prefix = \"portable-atomic-1.13.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.portable-atomic-1.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__portable-atomic-util-0.2.5\",\n sha256 = \"7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/portable-atomic-util/0.2.5/download\"],\n strip_prefix = \"portable-atomic-util-0.2.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.portable-atomic-util-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__potential_utf-0.1.4\",\n sha256 = \"b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/potential_utf/0.1.4/download\"],\n strip_prefix = \"potential_utf-0.1.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.potential_utf-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__powerfmt-0.2.0\",\n sha256 = \"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/powerfmt/0.2.0/download\"],\n strip_prefix = \"powerfmt-0.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.powerfmt-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ppv-lite86-0.2.21\",\n sha256 = \"85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ppv-lite86/0.2.21/download\"],\n strip_prefix = \"ppv-lite86-0.2.21\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ppv-lite86-0.2.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__precomputed-hash-0.1.1\",\n sha256 = \"925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/precomputed-hash/0.1.1/download\"],\n strip_prefix = \"precomputed-hash-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.precomputed-hash-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__prettyplease-0.2.37\",\n sha256 = \"479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prettyplease/0.2.37/download\"],\n strip_prefix = \"prettyplease-0.2.37\",\n build_file = Label(\"@crates_io//crates_io:BUILD.prettyplease-0.2.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__proc-macro-crate-1.3.1\",\n sha256 = \"7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro-crate/1.3.1/download\"],\n strip_prefix = \"proc-macro-crate-1.3.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.proc-macro-crate-1.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@crates_io//crates_io:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__prost-0.13.5\",\n sha256 = \"2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost/0.13.5/download\"],\n strip_prefix = \"prost-0.13.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.prost-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__prost-derive-0.13.5\",\n sha256 = \"8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost-derive/0.13.5/download\"],\n strip_prefix = \"prost-derive-0.13.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.prost-derive-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__prost-types-0.13.5\",\n sha256 = \"52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost-types/0.13.5/download\"],\n strip_prefix = \"prost-types-0.13.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.prost-types-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__psl-types-2.0.11\",\n sha256 = \"33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/psl-types/2.0.11/download\"],\n strip_prefix = \"psl-types-2.0.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.psl-types-2.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__publicsuffix-2.3.0\",\n sha256 = \"6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/publicsuffix/2.3.0/download\"],\n strip_prefix = \"publicsuffix-2.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.publicsuffix-2.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quick-error-1.2.3\",\n sha256 = \"a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quick-error/1.2.3/download\"],\n strip_prefix = \"quick-error-1.2.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quick-error-1.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quick-xml-0.39.2\",\n sha256 = \"958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quick-xml/0.39.2/download\"],\n strip_prefix = \"quick-xml-0.39.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quick-xml-0.39.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quinn-0.11.9\",\n sha256 = \"b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quinn/0.11.9/download\"],\n strip_prefix = \"quinn-0.11.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quinn-0.11.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quinn-proto-0.11.14\",\n sha256 = \"434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quinn-proto/0.11.14/download\"],\n strip_prefix = \"quinn-proto-0.11.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quinn-proto-0.11.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quinn-udp-0.5.14\",\n sha256 = \"addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quinn-udp/0.5.14/download\"],\n strip_prefix = \"quinn-udp-0.5.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quinn-udp-0.5.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@crates_io//crates_io:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__r-efi-5.3.0\",\n sha256 = \"69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/r-efi/5.3.0/download\"],\n strip_prefix = \"r-efi-5.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.r-efi-5.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__r-efi-6.0.0\",\n sha256 = \"f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/r-efi/6.0.0/download\"],\n strip_prefix = \"r-efi-6.0.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.r-efi-6.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand-0.8.5\",\n sha256 = \"34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand/0.8.5/download\"],\n strip_prefix = \"rand-0.8.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand-0.8.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand-0.9.2\",\n sha256 = \"6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand/0.9.2/download\"],\n strip_prefix = \"rand-0.9.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand_chacha-0.3.1\",\n sha256 = \"e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_chacha/0.3.1/download\"],\n strip_prefix = \"rand_chacha-0.3.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand_chacha-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand_chacha-0.9.0\",\n sha256 = \"d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_chacha/0.9.0/download\"],\n strip_prefix = \"rand_chacha-0.9.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand_chacha-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand_core-0.6.4\",\n sha256 = \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.6.4/download\"],\n strip_prefix = \"rand_core-0.6.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand_core-0.6.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand_core-0.9.5\",\n sha256 = \"76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.9.5/download\"],\n strip_prefix = \"rand_core-0.9.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand_core-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rand_distr-0.4.3\",\n sha256 = \"32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_distr/0.4.3/download\"],\n strip_prefix = \"rand_distr-0.4.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rand_distr-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rawpointer-0.2.1\",\n sha256 = \"60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rawpointer/0.2.1/download\"],\n strip_prefix = \"rawpointer-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rawpointer-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rayon-1.11.0\",\n sha256 = \"368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon/1.11.0/download\"],\n strip_prefix = \"rayon-1.11.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rayon-1.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rayon-core-1.13.0\",\n sha256 = \"22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rayon-core/1.13.0/download\"],\n strip_prefix = \"rayon-core-1.13.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rayon-core-1.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__redox_syscall-0.5.18\",\n sha256 = \"ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/redox_syscall/0.5.18/download\"],\n strip_prefix = \"redox_syscall-0.5.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.redox_syscall-0.5.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__redox_syscall-0.7.3\",\n sha256 = \"6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/redox_syscall/0.7.3/download\"],\n strip_prefix = \"redox_syscall-0.7.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.redox_syscall-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__regex-1.12.3\",\n sha256 = \"e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex/1.12.3/download\"],\n strip_prefix = \"regex-1.12.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.regex-1.12.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__regex-automata-0.4.14\",\n sha256 = \"6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-automata/0.4.14/download\"],\n strip_prefix = \"regex-automata-0.4.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.regex-automata-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__regex-syntax-0.8.10\",\n sha256 = \"dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-syntax/0.8.10/download\"],\n strip_prefix = \"regex-syntax-0.8.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.regex-syntax-0.8.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__reqwest-0.12.28\",\n sha256 = \"eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/reqwest/0.12.28/download\"],\n strip_prefix = \"reqwest-0.12.28\",\n build_file = Label(\"@crates_io//crates_io:BUILD.reqwest-0.12.28.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__reqwest-0.13.2\",\n sha256 = \"ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/reqwest/0.13.2/download\"],\n strip_prefix = \"reqwest-0.13.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.reqwest-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ring-0.17.14\",\n sha256 = \"a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ring/0.17.14/download\"],\n strip_prefix = \"ring-0.17.14\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ring-0.17.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rsa-0.9.10\",\n sha256 = \"b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rsa/0.9.10/download\"],\n strip_prefix = \"rsa-0.9.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rsa-0.9.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustc-hash-2.1.1\",\n sha256 = \"357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-hash/2.1.1/download\"],\n strip_prefix = \"rustc-hash-2.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustc-hash-2.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustc_version-0.4.1\",\n sha256 = \"cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc_version/0.4.1/download\"],\n strip_prefix = \"rustc_version-0.4.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustc_version-0.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustix-0.38.44\",\n sha256 = \"fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/0.38.44/download\"],\n strip_prefix = \"rustix-0.38.44\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustix-0.38.44.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustix-1.1.4\",\n sha256 = \"b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.4/download\"],\n strip_prefix = \"rustix-1.1.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustix-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-0.23.37\",\n sha256 = \"758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls/0.23.37/download\"],\n strip_prefix = \"rustls-0.23.37\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-0.23.37.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-native-certs-0.8.3\",\n sha256 = \"612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-native-certs/0.8.3/download\"],\n strip_prefix = \"rustls-native-certs-0.8.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-native-certs-0.8.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-pki-types-1.14.0\",\n sha256 = \"be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-pki-types/1.14.0/download\"],\n strip_prefix = \"rustls-pki-types-1.14.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-pki-types-1.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-platform-verifier-0.6.2\",\n sha256 = \"1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-platform-verifier/0.6.2/download\"],\n strip_prefix = \"rustls-platform-verifier-0.6.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-platform-verifier-0.6.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-platform-verifier-android-0.1.1\",\n sha256 = \"f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-platform-verifier-android/0.1.1/download\"],\n strip_prefix = \"rustls-platform-verifier-android-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-platform-verifier-android-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustls-webpki-0.103.9\",\n sha256 = \"d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustls-webpki/0.103.9/download\"],\n strip_prefix = \"rustls-webpki-0.103.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustls-webpki-0.103.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__rustversion-1.0.22\",\n sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n strip_prefix = \"rustversion-1.0.22\",\n build_file = Label(\"@crates_io//crates_io:BUILD.rustversion-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ryu-1.0.23\",\n sha256 = \"9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ryu/1.0.23/download\"],\n strip_prefix = \"ryu-1.0.23\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ryu-1.0.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__safe_arch-0.7.4\",\n sha256 = \"96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/safe_arch/0.7.4/download\"],\n strip_prefix = \"safe_arch-0.7.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.safe_arch-0.7.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__same-file-1.0.6\",\n sha256 = \"93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/same-file/1.0.6/download\"],\n strip_prefix = \"same-file-1.0.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.same-file-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__schannel-0.1.29\",\n sha256 = \"91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/schannel/0.1.29/download\"],\n strip_prefix = \"schannel-0.1.29\",\n build_file = Label(\"@crates_io//crates_io:BUILD.schannel-0.1.29.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__scoped-tls-1.0.1\",\n sha256 = \"e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scoped-tls/1.0.1/download\"],\n strip_prefix = \"scoped-tls-1.0.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.scoped-tls-1.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__scopeguard-1.2.0\",\n sha256 = \"94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scopeguard/1.2.0/download\"],\n strip_prefix = \"scopeguard-1.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.scopeguard-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__security-framework-3.7.0\",\n sha256 = \"b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/security-framework/3.7.0/download\"],\n strip_prefix = \"security-framework-3.7.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.security-framework-3.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__security-framework-sys-2.17.0\",\n sha256 = \"6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/security-framework-sys/2.17.0/download\"],\n strip_prefix = \"security-framework-sys-2.17.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.security-framework-sys-2.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__selectors-0.25.0\",\n sha256 = \"4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/selectors/0.25.0/download\"],\n strip_prefix = \"selectors-0.25.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.selectors-0.25.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__selectors-0.33.0\",\n sha256 = \"feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/selectors/0.33.0/download\"],\n strip_prefix = \"selectors-0.33.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.selectors-0.33.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__semver-1.0.27\",\n sha256 = \"d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/1.0.27/download\"],\n strip_prefix = \"semver-1.0.27\",\n build_file = Label(\"@crates_io//crates_io:BUILD.semver-1.0.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_json-1.0.149\",\n sha256 = \"83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json/1.0.149/download\"],\n strip_prefix = \"serde_json-1.0.149\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_json-1.0.149.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_regex-1.1.0\",\n sha256 = \"a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_regex/1.1.0/download\"],\n strip_prefix = \"serde_regex-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_regex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_spanned-1.0.4\",\n sha256 = \"f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_spanned/1.0.4/download\"],\n strip_prefix = \"serde_spanned-1.0.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_spanned-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__serde_urlencoded-0.7.1\",\n sha256 = \"d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_urlencoded/0.7.1/download\"],\n strip_prefix = \"serde_urlencoded-0.7.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.serde_urlencoded-0.7.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__servo_arc-0.3.0\",\n sha256 = \"d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/servo_arc/0.3.0/download\"],\n strip_prefix = \"servo_arc-0.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.servo_arc-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__servo_arc-0.4.3\",\n sha256 = \"170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/servo_arc/0.4.3/download\"],\n strip_prefix = \"servo_arc-0.4.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.servo_arc-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sha1-0.10.6\",\n sha256 = \"e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha1/0.10.6/download\"],\n strip_prefix = \"sha1-0.10.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sha1-0.10.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sha2-0.10.9\",\n sha256 = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha2/0.10.9/download\"],\n strip_prefix = \"sha2-0.10.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sha2-0.10.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sharded-slab-0.1.7\",\n sha256 = \"f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sharded-slab/0.1.7/download\"],\n strip_prefix = \"sharded-slab-0.1.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sharded-slab-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__shlex-1.3.0\",\n sha256 = \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/shlex/1.3.0/download\"],\n strip_prefix = \"shlex-1.3.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.shlex-1.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__signal-hook-registry-1.4.8\",\n sha256 = \"c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signal-hook-registry/1.4.8/download\"],\n strip_prefix = \"signal-hook-registry-1.4.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.signal-hook-registry-1.4.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__signature-2.2.0\",\n sha256 = \"77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signature/2.2.0/download\"],\n strip_prefix = \"signature-2.2.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.signature-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__simba-0.9.1\",\n sha256 = \"c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/simba/0.9.1/download\"],\n strip_prefix = \"simba-0.9.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.simba-0.9.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__simd-adler32-0.3.8\",\n sha256 = \"e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/simd-adler32/0.3.8/download\"],\n strip_prefix = \"simd-adler32-0.3.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.simd-adler32-0.3.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__siphasher-0.3.11\",\n sha256 = \"38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/siphasher/0.3.11/download\"],\n strip_prefix = \"siphasher-0.3.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.siphasher-0.3.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__siphasher-1.0.2\",\n sha256 = \"b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/siphasher/1.0.2/download\"],\n strip_prefix = \"siphasher-1.0.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.siphasher-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__slab-0.4.12\",\n sha256 = \"0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/slab/0.4.12/download\"],\n strip_prefix = \"slab-0.4.12\",\n build_file = Label(\"@crates_io//crates_io:BUILD.slab-0.4.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__smallvec-1.15.1\",\n sha256 = \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smallvec/1.15.1/download\"],\n strip_prefix = \"smallvec-1.15.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.smallvec-1.15.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__socket2-0.5.10\",\n sha256 = \"e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/socket2/0.5.10/download\"],\n strip_prefix = \"socket2-0.5.10\",\n build_file = Label(\"@crates_io//crates_io:BUILD.socket2-0.5.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__socket2-0.6.3\",\n sha256 = \"3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/socket2/0.6.3/download\"],\n strip_prefix = \"socket2-0.6.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.socket2-0.6.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider-2.47.18\",\n sha256 = \"d770e1b23232fa0af8aea8d95043dab6bcd730330042f656afa2a28f9fef6d46\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider/2.47.18/download\"],\n strip_prefix = \"spider-2.47.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider-2.47.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_agent_types-2.47.18\",\n sha256 = \"7812544c380670f38e7fd78c5f0eb8ceb6c5fb3b291aafbc6173ff4022f8df7f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_agent_types/2.47.18/download\"],\n strip_prefix = \"spider_agent_types-2.47.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_agent_types-2.47.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_chromiumoxide_cdp-0.7.9\",\n sha256 = \"425d07927c18fd157c4680cee8c7868e4a9bf4723bda1a4951c3316e3abdea4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_chromiumoxide_cdp/0.7.9/download\"],\n strip_prefix = \"spider_chromiumoxide_cdp-0.7.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_chromiumoxide_cdp-0.7.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_chromiumoxide_pdl-0.7.6\",\n sha256 = \"4090793264cfc7533186505f7cc2198ef03325dfcbfe32d0e6814c6ca759051f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_chromiumoxide_pdl/0.7.6/download\"],\n strip_prefix = \"spider_chromiumoxide_pdl-0.7.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_chromiumoxide_pdl-0.7.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_chromiumoxide_types-0.7.5\",\n sha256 = \"dec8ac95a73fa31063b306dcbb48a2b3115fde2ff9152004148eb152844917e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_chromiumoxide_types/0.7.5/download\"],\n strip_prefix = \"spider_chromiumoxide_types-0.7.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_chromiumoxide_types-0.7.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_fingerprint-2.38.1\",\n sha256 = \"f4e26862e5b31d1292804e64d59601af1594b1ab6e0474c2b62dcd447497ae89\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_fingerprint/2.38.1/download\"],\n strip_prefix = \"spider_fingerprint-2.38.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_fingerprint-2.38.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_network_blocker-0.0.58\",\n sha256 = \"19cf37feaf9b5f9202d060f8cebc213c2833aecd462841749ab3d4d39d1db3ba\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_network_blocker/0.0.58/download\"],\n strip_prefix = \"spider_network_blocker-0.0.58\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_network_blocker-0.0.58.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_scraper-0.1.2\",\n sha256 = \"323b02db860415a5414ac0a55a10e830df133b7f3cd877178e5889bc9fba01d3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_scraper/0.1.2/download\"],\n strip_prefix = \"spider_scraper-0.1.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_scraper-0.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_transformations-2.37.115\",\n sha256 = \"c23d3c922058c56970357725d8ded379188314cbe99fb1611531542f170dbcb0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_transformations/2.37.115/download\"],\n strip_prefix = \"spider_transformations-2.37.115\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_transformations-2.37.115.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spider_utils-2.47.18\",\n sha256 = \"8f0f3b2babd77b2232899c20b044111fae13eec6af4445e5b51727a35f9aec75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spider_utils/2.47.18/download\"],\n strip_prefix = \"spider_utils-2.47.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spider_utils-2.47.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spin-0.9.8\",\n sha256 = \"6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spin/0.9.8/download\"],\n strip_prefix = \"spin-0.9.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spin-0.9.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__spki-0.7.3\",\n sha256 = \"d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/spki/0.7.3/download\"],\n strip_prefix = \"spki-0.7.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.spki-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-0.8.6\",\n sha256 = \"1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx/0.8.6/download\"],\n strip_prefix = \"sqlx-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-core-0.8.6\",\n sha256 = \"ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-core/0.8.6/download\"],\n strip_prefix = \"sqlx-core-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-core-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-macros-0.8.6\",\n sha256 = \"a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-macros/0.8.6/download\"],\n strip_prefix = \"sqlx-macros-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-macros-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-macros-core-0.8.6\",\n sha256 = \"19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-macros-core/0.8.6/download\"],\n strip_prefix = \"sqlx-macros-core-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-macros-core-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-mysql-0.8.6\",\n sha256 = \"aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-mysql/0.8.6/download\"],\n strip_prefix = \"sqlx-mysql-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-mysql-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-postgres-0.8.6\",\n sha256 = \"db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-postgres/0.8.6/download\"],\n strip_prefix = \"sqlx-postgres-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-postgres-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sqlx-sqlite-0.8.6\",\n sha256 = \"c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sqlx-sqlite/0.8.6/download\"],\n strip_prefix = \"sqlx-sqlite-0.8.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sqlx-sqlite-0.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__stable_deref_trait-1.2.1\",\n sha256 = \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/stable_deref_trait/1.2.1/download\"],\n strip_prefix = \"stable_deref_trait-1.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.stable_deref_trait-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__static_assertions-1.1.0\",\n sha256 = \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/static_assertions/1.1.0/download\"],\n strip_prefix = \"static_assertions-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.static_assertions-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__statrs-0.18.0\",\n sha256 = \"2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/statrs/0.18.0/download\"],\n strip_prefix = \"statrs-0.18.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.statrs-0.18.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__string-interner-0.19.0\",\n sha256 = \"23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string-interner/0.19.0/download\"],\n strip_prefix = \"string-interner-0.19.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.string-interner-0.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__string_cache-0.8.9\",\n sha256 = \"bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string_cache/0.8.9/download\"],\n strip_prefix = \"string_cache-0.8.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.string_cache-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__string_cache_codegen-0.5.4\",\n sha256 = \"c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string_cache_codegen/0.5.4/download\"],\n strip_prefix = \"string_cache_codegen-0.5.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.string_cache_codegen-0.5.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__string_concat-0.0.1\",\n sha256 = \"c3c3ee6129eec20fed59acf2e9cfb3ffd20d0bbe39fe334c22af0edc56dfe752\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string_concat/0.0.1/download\"],\n strip_prefix = \"string_concat-0.0.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.string_concat-0.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__stringprep-0.1.5\",\n sha256 = \"7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/stringprep/0.1.5/download\"],\n strip_prefix = \"stringprep-0.1.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.stringprep-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__strum-0.27.2\",\n sha256 = \"af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum/0.27.2/download\"],\n strip_prefix = \"strum-0.27.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.strum-0.27.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__strum_macros-0.27.2\",\n sha256 = \"7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strum_macros/0.27.2/download\"],\n strip_prefix = \"strum_macros-0.27.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.strum_macros-0.27.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__subtle-2.6.1\",\n sha256 = \"13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/subtle/2.6.1/download\"],\n strip_prefix = \"subtle-2.6.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.subtle-2.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sxd-document-0.3.2\",\n sha256 = \"94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sxd-document/0.3.2/download\"],\n strip_prefix = \"sxd-document-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sxd-document-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sxd-xpath-0.4.2\",\n sha256 = \"36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sxd-xpath/0.4.2/download\"],\n strip_prefix = \"sxd-xpath-0.4.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sxd-xpath-0.4.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__syn-1.0.109\",\n sha256 = \"72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/1.0.109/download\"],\n strip_prefix = \"syn-1.0.109\",\n build_file = Label(\"@crates_io//crates_io:BUILD.syn-1.0.109.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@crates_io//crates_io:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sync_wrapper-1.0.2\",\n sha256 = \"0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sync_wrapper/1.0.2/download\"],\n strip_prefix = \"sync_wrapper-1.0.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sync_wrapper-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__synstructure-0.12.6\",\n sha256 = \"f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/synstructure/0.12.6/download\"],\n strip_prefix = \"synstructure-0.12.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.synstructure-0.12.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__synstructure-0.13.2\",\n sha256 = \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/synstructure/0.13.2/download\"],\n strip_prefix = \"synstructure-0.13.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.synstructure-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__sysinfo-0.38.4\",\n sha256 = \"92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sysinfo/0.38.4/download\"],\n strip_prefix = \"sysinfo-0.38.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.sysinfo-0.38.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__system-configuration-0.7.0\",\n sha256 = \"a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/system-configuration/0.7.0/download\"],\n strip_prefix = \"system-configuration-0.7.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.system-configuration-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__system-configuration-sys-0.6.0\",\n sha256 = \"8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/system-configuration-sys/0.6.0/download\"],\n strip_prefix = \"system-configuration-sys-0.6.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.system-configuration-sys-0.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tempfile-3.27.0\",\n sha256 = \"32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tempfile/3.27.0/download\"],\n strip_prefix = \"tempfile-3.27.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tempfile-3.27.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tendril-0.4.3\",\n sha256 = \"d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tendril/0.4.3/download\"],\n strip_prefix = \"tendril-0.4.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tendril-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__thiserror-1.0.69\",\n sha256 = \"b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/1.0.69/download\"],\n strip_prefix = \"thiserror-1.0.69\",\n build_file = Label(\"@crates_io//crates_io:BUILD.thiserror-1.0.69.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__thiserror-2.0.18\",\n sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n strip_prefix = \"thiserror-2.0.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.thiserror-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__thiserror-impl-1.0.69\",\n sha256 = \"4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/1.0.69/download\"],\n strip_prefix = \"thiserror-impl-1.0.69\",\n build_file = Label(\"@crates_io//crates_io:BUILD.thiserror-impl-1.0.69.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__thiserror-impl-2.0.18\",\n sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n strip_prefix = \"thiserror-impl-2.0.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.thiserror-impl-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__thread_local-1.1.9\",\n sha256 = \"f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thread_local/1.1.9/download\"],\n strip_prefix = \"thread_local-1.1.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.thread_local-1.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__time-0.3.47\",\n sha256 = \"743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time/0.3.47/download\"],\n strip_prefix = \"time-0.3.47\",\n build_file = Label(\"@crates_io//crates_io:BUILD.time-0.3.47.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__time-core-0.1.8\",\n sha256 = \"7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time-core/0.1.8/download\"],\n strip_prefix = \"time-core-0.1.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.time-core-0.1.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__time-macros-0.2.27\",\n sha256 = \"2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/time-macros/0.2.27/download\"],\n strip_prefix = \"time-macros-0.2.27\",\n build_file = Label(\"@crates_io//crates_io:BUILD.time-macros-0.2.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tinystr-0.8.2\",\n sha256 = \"42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinystr/0.8.2/download\"],\n strip_prefix = \"tinystr-0.8.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tinystr-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tinytemplate-1.2.1\",\n sha256 = \"be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinytemplate/1.2.1/download\"],\n strip_prefix = \"tinytemplate-1.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tinytemplate-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tinyvec-1.10.0\",\n sha256 = \"bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec/1.10.0/download\"],\n strip_prefix = \"tinyvec-1.10.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tinyvec-1.10.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tinyvec_macros-0.1.1\",\n sha256 = \"1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tinyvec_macros/0.1.1/download\"],\n strip_prefix = \"tinyvec_macros-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tinyvec_macros-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-1.50.0\",\n sha256 = \"27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio/1.50.0/download\"],\n strip_prefix = \"tokio-1.50.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-1.50.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-macros-2.6.1\",\n sha256 = \"5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-macros/2.6.1/download\"],\n strip_prefix = \"tokio-macros-2.6.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-macros-2.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-rustls-0.26.4\",\n sha256 = \"1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-rustls/0.26.4/download\"],\n strip_prefix = \"tokio-rustls-0.26.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-rustls-0.26.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-stream-0.1.18\",\n sha256 = \"32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-stream/0.1.18/download\"],\n strip_prefix = \"tokio-stream-0.1.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-stream-0.1.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-tungstenite-0.21.0\",\n sha256 = \"c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-tungstenite/0.21.0/download\"],\n strip_prefix = \"tokio-tungstenite-0.21.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-tungstenite-0.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-tungstenite-0.28.0\",\n sha256 = \"d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-tungstenite/0.28.0/download\"],\n strip_prefix = \"tokio-tungstenite-0.28.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-tungstenite-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tokio-util-0.7.18\",\n sha256 = \"9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-util/0.7.18/download\"],\n strip_prefix = \"tokio-util-0.7.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tokio-util-0.7.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml-0.9.12-spec-1.1.0\",\n sha256 = \"cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml/0.9.12+spec-1.1.0/download\"],\n strip_prefix = \"toml-0.9.12+spec-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml-0.9.12+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml_datetime-0.6.11\",\n sha256 = \"22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_datetime/0.6.11/download\"],\n strip_prefix = \"toml_datetime-0.6.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml_datetime-0.6.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml_datetime-0.7.5-spec-1.1.0\",\n sha256 = \"92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_datetime/0.7.5+spec-1.1.0/download\"],\n strip_prefix = \"toml_datetime-0.7.5+spec-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml_datetime-0.7.5+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml_edit-0.19.15\",\n sha256 = \"1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_edit/0.19.15/download\"],\n strip_prefix = \"toml_edit-0.19.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml_edit-0.19.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml_parser-1.0.9-spec-1.1.0\",\n sha256 = \"702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_parser/1.0.9+spec-1.1.0/download\"],\n strip_prefix = \"toml_parser-1.0.9+spec-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml_parser-1.0.9+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__toml_writer-1.0.6-spec-1.1.0\",\n sha256 = \"ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/toml_writer/1.0.6+spec-1.1.0/download\"],\n strip_prefix = \"toml_writer-1.0.6+spec-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.toml_writer-1.0.6+spec-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tonic-0.12.3\",\n sha256 = \"877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tonic/0.12.3/download\"],\n strip_prefix = \"tonic-0.12.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tonic-0.12.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tower-0.4.13\",\n sha256 = \"b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower/0.4.13/download\"],\n strip_prefix = \"tower-0.4.13\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tower-0.4.13.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tower-0.5.3\",\n sha256 = \"ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower/0.5.3/download\"],\n strip_prefix = \"tower-0.5.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tower-0.5.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tower-http-0.6.8\",\n sha256 = \"d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-http/0.6.8/download\"],\n strip_prefix = \"tower-http-0.6.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tower-http-0.6.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tower-layer-0.3.3\",\n sha256 = \"121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-layer/0.3.3/download\"],\n strip_prefix = \"tower-layer-0.3.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tower-layer-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tower-service-0.3.3\",\n sha256 = \"8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tower-service/0.3.3/download\"],\n strip_prefix = \"tower-service-0.3.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tower-service-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tracing-0.1.44\",\n sha256 = \"63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing/0.1.44/download\"],\n strip_prefix = \"tracing-0.1.44\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tracing-0.1.44.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tracing-attributes-0.1.31\",\n sha256 = \"7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing-attributes/0.1.31/download\"],\n strip_prefix = \"tracing-attributes-0.1.31\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tracing-attributes-0.1.31.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tracing-core-0.1.36\",\n sha256 = \"db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing-core/0.1.36/download\"],\n strip_prefix = \"tracing-core-0.1.36\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tracing-core-0.1.36.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tracing-subscriber-0.3.23\",\n sha256 = \"cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tracing-subscriber/0.3.23/download\"],\n strip_prefix = \"tracing-subscriber-0.3.23\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tracing-subscriber-0.3.23.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__try-lock-0.2.5\",\n sha256 = \"e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/try-lock/0.2.5/download\"],\n strip_prefix = \"try-lock-0.2.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.try-lock-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tungstenite-0.21.0\",\n sha256 = \"9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tungstenite/0.21.0/download\"],\n strip_prefix = \"tungstenite-0.21.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tungstenite-0.21.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__tungstenite-0.28.0\",\n sha256 = \"8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tungstenite/0.28.0/download\"],\n strip_prefix = \"tungstenite-0.28.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.tungstenite-0.28.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__typed-arena-1.7.0\",\n sha256 = \"a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typed-arena/1.7.0/download\"],\n strip_prefix = \"typed-arena-1.7.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.typed-arena-1.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__typenum-1.19.0\",\n sha256 = \"562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typenum/1.19.0/download\"],\n strip_prefix = \"typenum-1.19.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.typenum-1.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ua_generator-0.5.52\",\n sha256 = \"3eebe5bf263176003918e31d3789d37dc848f142b72da8eb9cbd8b0e067287e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ua_generator/0.5.52/download\"],\n strip_prefix = \"ua_generator-0.5.52\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ua_generator-0.5.52.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicase-2.9.0\",\n sha256 = \"dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicase/2.9.0/download\"],\n strip_prefix = \"unicase-2.9.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicase-2.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-bidi-0.3.18\",\n sha256 = \"5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-bidi/0.3.18/download\"],\n strip_prefix = \"unicode-bidi-0.3.18\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-bidi-0.3.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-normalization-0.1.25\",\n sha256 = \"5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-normalization/0.1.25/download\"],\n strip_prefix = \"unicode-normalization-0.1.25\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-normalization-0.1.25.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-properties-0.1.4\",\n sha256 = \"7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-properties/0.1.4/download\"],\n strip_prefix = \"unicode-properties-0.1.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-properties-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-segmentation-1.12.0\",\n sha256 = \"f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-segmentation/1.12.0/download\"],\n strip_prefix = \"unicode-segmentation-1.12.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-segmentation-1.12.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-width-0.2.2\",\n sha256 = \"b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-width/0.2.2/download\"],\n strip_prefix = \"unicode-width-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-width-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__unicode-xid-0.2.6\",\n sha256 = \"ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-xid/0.2.6/download\"],\n strip_prefix = \"unicode-xid-0.2.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.unicode-xid-0.2.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__untrusted-0.9.0\",\n sha256 = \"8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/untrusted/0.9.0/download\"],\n strip_prefix = \"untrusted-0.9.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.untrusted-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__ureq-2.12.1\",\n sha256 = \"02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ureq/2.12.1/download\"],\n strip_prefix = \"ureq-2.12.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.ureq-2.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__url-2.5.8\",\n sha256 = \"ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/url/2.5.8/download\"],\n strip_prefix = \"url-2.5.8\",\n build_file = Label(\"@crates_io//crates_io:BUILD.url-2.5.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__utf-8-0.7.6\",\n sha256 = \"09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf-8/0.7.6/download\"],\n strip_prefix = \"utf-8-0.7.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.utf-8-0.7.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__utf8_iter-1.0.4\",\n sha256 = \"b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8_iter/1.0.4/download\"],\n strip_prefix = \"utf8_iter-1.0.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.utf8_iter-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__valuable-0.1.1\",\n sha256 = \"ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/valuable/0.1.1/download\"],\n strip_prefix = \"valuable-0.1.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.valuable-0.1.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__vcpkg-0.2.15\",\n sha256 = \"accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/vcpkg/0.2.15/download\"],\n strip_prefix = \"vcpkg-0.2.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.vcpkg-0.2.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__version_check-0.9.5\",\n sha256 = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/version_check/0.9.5/download\"],\n strip_prefix = \"version_check-0.9.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.version_check-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__walkdir-2.5.0\",\n sha256 = \"29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/walkdir/2.5.0/download\"],\n strip_prefix = \"walkdir-2.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.walkdir-2.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__want-0.3.1\",\n sha256 = \"bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/want/0.3.1/download\"],\n strip_prefix = \"want-0.3.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.want-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__warp-0.3.7\",\n sha256 = \"4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/warp/0.3.7/download\"],\n strip_prefix = \"warp-0.3.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.warp-0.3.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasip2-1.0.2-wasi-0.2.9\",\n sha256 = \"9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip2/1.0.2+wasi-0.2.9/download\"],\n strip_prefix = \"wasip2-1.0.2+wasi-0.2.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasip2-1.0.2+wasi-0.2.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasip3-0.4.0-wasi-0.3.0-rc-2026-01-06\",\n sha256 = \"5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasip3/0.4.0+wasi-0.3.0-rc-2026-01-06/download\"],\n strip_prefix = \"wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasite-0.1.0\",\n sha256 = \"b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasite/0.1.0/download\"],\n strip_prefix = \"wasite-0.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasite-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-bindgen-0.2.114\",\n sha256 = \"6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen/0.2.114/download\"],\n strip_prefix = \"wasm-bindgen-0.2.114\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-bindgen-0.2.114.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-bindgen-futures-0.4.64\",\n sha256 = \"e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-futures/0.4.64/download\"],\n strip_prefix = \"wasm-bindgen-futures-0.4.64\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-bindgen-futures-0.4.64.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-bindgen-macro-0.2.114\",\n sha256 = \"18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro/0.2.114/download\"],\n strip_prefix = \"wasm-bindgen-macro-0.2.114\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-bindgen-macro-0.2.114.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-bindgen-macro-support-0.2.114\",\n sha256 = \"03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.114/download\"],\n strip_prefix = \"wasm-bindgen-macro-support-0.2.114\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-bindgen-macro-support-0.2.114.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-bindgen-shared-0.2.114\",\n sha256 = \"75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-bindgen-shared/0.2.114/download\"],\n strip_prefix = \"wasm-bindgen-shared-0.2.114\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-bindgen-shared-0.2.114.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-encoder-0.244.0\",\n sha256 = \"990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-encoder/0.244.0/download\"],\n strip_prefix = \"wasm-encoder-0.244.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-encoder-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-metadata-0.244.0\",\n sha256 = \"bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-metadata/0.244.0/download\"],\n strip_prefix = \"wasm-metadata-0.244.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-metadata-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasm-streams-0.5.0\",\n sha256 = \"9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasm-streams/0.5.0/download\"],\n strip_prefix = \"wasm-streams-0.5.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasm-streams-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wasmparser-0.244.0\",\n sha256 = \"47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasmparser/0.244.0/download\"],\n strip_prefix = \"wasmparser-0.244.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wasmparser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__web-sys-0.3.91\",\n sha256 = \"854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-sys/0.3.91/download\"],\n strip_prefix = \"web-sys-0.3.91\",\n build_file = Label(\"@crates_io//crates_io:BUILD.web-sys-0.3.91.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__web-time-1.1.0\",\n sha256 = \"5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/web-time/1.1.0/download\"],\n strip_prefix = \"web-time-1.1.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.web-time-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__webpki-root-certs-1.0.6\",\n sha256 = \"804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/webpki-root-certs/1.0.6/download\"],\n strip_prefix = \"webpki-root-certs-1.0.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.webpki-root-certs-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__webpki-roots-0.26.11\",\n sha256 = \"521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/webpki-roots/0.26.11/download\"],\n strip_prefix = \"webpki-roots-0.26.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.webpki-roots-0.26.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__webpki-roots-1.0.6\",\n sha256 = \"22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/webpki-roots/1.0.6/download\"],\n strip_prefix = \"webpki-roots-1.0.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.webpki-roots-1.0.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__which-6.0.3\",\n sha256 = \"b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/which/6.0.3/download\"],\n strip_prefix = \"which-6.0.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.which-6.0.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__whoami-1.6.1\",\n sha256 = \"5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/whoami/1.6.1/download\"],\n strip_prefix = \"whoami-1.6.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.whoami-1.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wide-0.7.33\",\n sha256 = \"0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wide/0.7.33/download\"],\n strip_prefix = \"wide-0.7.33\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wide-0.7.33.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winapi-0.3.9\",\n sha256 = \"5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi/0.3.9/download\"],\n strip_prefix = \"winapi-0.3.9\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winapi-0.3.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winapi-i686-pc-windows-gnu-0.4.0\",\n sha256 = \"ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-i686-pc-windows-gnu/0.4.0/download\"],\n strip_prefix = \"winapi-i686-pc-windows-gnu-0.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winapi-i686-pc-windows-gnu-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winapi-util-0.1.11\",\n sha256 = \"c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-util/0.1.11/download\"],\n strip_prefix = \"winapi-util-0.1.11\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winapi-util-0.1.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winapi-x86_64-pc-windows-gnu-0.4.0\",\n sha256 = \"712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winapi-x86_64-pc-windows-gnu/0.4.0/download\"],\n strip_prefix = \"winapi-x86_64-pc-windows-gnu-0.4.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winapi-x86_64-pc-windows-gnu-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-0.62.2\",\n sha256 = \"527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows/0.62.2/download\"],\n strip_prefix = \"windows-0.62.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-0.62.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-collections-0.3.2\",\n sha256 = \"23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-collections/0.3.2/download\"],\n strip_prefix = \"windows-collections-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-collections-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-core-0.62.2\",\n sha256 = \"b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-core/0.62.2/download\"],\n strip_prefix = \"windows-core-0.62.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-core-0.62.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-future-0.3.2\",\n sha256 = \"e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-future/0.3.2/download\"],\n strip_prefix = \"windows-future-0.3.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-future-0.3.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-implement-0.60.2\",\n sha256 = \"053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-implement/0.60.2/download\"],\n strip_prefix = \"windows-implement-0.60.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-implement-0.60.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-interface-0.59.3\",\n sha256 = \"3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-interface/0.59.3/download\"],\n strip_prefix = \"windows-interface-0.59.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-interface-0.59.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-numerics-0.3.1\",\n sha256 = \"6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-numerics/0.3.1/download\"],\n strip_prefix = \"windows-numerics-0.3.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-numerics-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-registry-0.6.1\",\n sha256 = \"02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-registry/0.6.1/download\"],\n strip_prefix = \"windows-registry-0.6.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-registry-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-result-0.4.1\",\n sha256 = \"7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-result/0.4.1/download\"],\n strip_prefix = \"windows-result-0.4.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-result-0.4.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-strings-0.5.1\",\n sha256 = \"7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-strings/0.5.1/download\"],\n strip_prefix = \"windows-strings-0.5.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-strings-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.45.0\",\n sha256 = \"75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.45.0/download\"],\n strip_prefix = \"windows-sys-0.45.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.45.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.48.0\",\n sha256 = \"677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.48.0/download\"],\n strip_prefix = \"windows-sys-0.48.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.48.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.52.0\",\n sha256 = \"282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.52.0/download\"],\n strip_prefix = \"windows-sys-0.52.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.52.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.59.0\",\n sha256 = \"1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.59.0/download\"],\n strip_prefix = \"windows-sys-0.59.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.59.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.60.2\",\n sha256 = \"f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.60.2/download\"],\n strip_prefix = \"windows-sys-0.60.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.60.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-targets-0.42.2\",\n sha256 = \"8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.42.2/download\"],\n strip_prefix = \"windows-targets-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-targets-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-targets-0.48.5\",\n sha256 = \"9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.48.5/download\"],\n strip_prefix = \"windows-targets-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-targets-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-targets-0.52.6\",\n sha256 = \"9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.52.6/download\"],\n strip_prefix = \"windows-targets-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-targets-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-targets-0.53.5\",\n sha256 = \"4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-targets/0.53.5/download\"],\n strip_prefix = \"windows-targets-0.53.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-targets-0.53.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows-threading-0.2.1\",\n sha256 = \"3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-threading/0.2.1/download\"],\n strip_prefix = \"windows-threading-0.2.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows-threading-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_gnullvm-0.42.2\",\n sha256 = \"597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.42.2/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_gnullvm-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_gnullvm-0.48.5\",\n sha256 = \"2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.48.5/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_gnullvm-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_gnullvm-0.52.6\",\n sha256 = \"32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_gnullvm-0.53.1\",\n sha256 = \"a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_aarch64_gnullvm-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_msvc-0.42.2\",\n sha256 = \"e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.42.2/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_msvc-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_msvc-0.48.5\",\n sha256 = \"dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.48.5/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_msvc-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_msvc-0.52.6\",\n sha256 = \"09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_aarch64_msvc-0.53.1\",\n sha256 = \"b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_aarch64_msvc/0.53.1/download\"],\n strip_prefix = \"windows_aarch64_msvc-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_aarch64_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnu-0.42.2\",\n sha256 = \"c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.42.2/download\"],\n strip_prefix = \"windows_i686_gnu-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnu-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnu-0.48.5\",\n sha256 = \"a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.48.5/download\"],\n strip_prefix = \"windows_i686_gnu-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnu-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnu-0.52.6\",\n sha256 = \"8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnu-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnu-0.53.1\",\n sha256 = \"960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnu/0.53.1/download\"],\n strip_prefix = \"windows_i686_gnu-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnu-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnullvm-0.52.6\",\n sha256 = \"0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_gnullvm-0.53.1\",\n sha256 = \"fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_i686_gnullvm-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_msvc-0.42.2\",\n sha256 = \"44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.42.2/download\"],\n strip_prefix = \"windows_i686_msvc-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_msvc-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_msvc-0.48.5\",\n sha256 = \"8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.48.5/download\"],\n strip_prefix = \"windows_i686_msvc-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_msvc-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_msvc-0.52.6\",\n sha256 = \"240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.52.6/download\"],\n strip_prefix = \"windows_i686_msvc-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_i686_msvc-0.53.1\",\n sha256 = \"1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_i686_msvc/0.53.1/download\"],\n strip_prefix = \"windows_i686_msvc-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_i686_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnu-0.42.2\",\n sha256 = \"8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.42.2/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnu-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnu-0.48.5\",\n sha256 = \"53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.48.5/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnu-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnu-0.52.6\",\n sha256 = \"147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnu-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnu-0.53.1\",\n sha256 = \"9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnu/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_gnu-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnu-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnullvm-0.42.2\",\n sha256 = \"26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.42.2/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnullvm-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnullvm-0.48.5\",\n sha256 = \"0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.48.5/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnullvm-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnullvm-0.52.6\",\n sha256 = \"24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnullvm-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_gnullvm-0.53.1\",\n sha256 = \"0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_gnullvm/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_gnullvm-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_gnullvm-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_msvc-0.42.2\",\n sha256 = \"9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.42.2/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.42.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_msvc-0.42.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_msvc-0.48.5\",\n sha256 = \"ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.48.5/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.48.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_msvc-0.48.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_msvc-0.52.6\",\n sha256 = \"589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.52.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_msvc-0.52.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__windows_x86_64_msvc-0.53.1\",\n sha256 = \"d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows_x86_64_msvc/0.53.1/download\"],\n strip_prefix = \"windows_x86_64_msvc-0.53.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.windows_x86_64_msvc-0.53.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winnow-0.5.40\",\n sha256 = \"f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/0.5.40/download\"],\n strip_prefix = \"winnow-0.5.40\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winnow-0.5.40.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winnow-0.7.15\",\n sha256 = \"df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winnow/0.7.15/download\"],\n strip_prefix = \"winnow-0.7.15\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winnow-0.7.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winreg-0.52.0\",\n sha256 = \"a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winreg/0.52.0/download\"],\n strip_prefix = \"winreg-0.52.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winreg-0.52.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__winsafe-0.0.19\",\n sha256 = \"d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/winsafe/0.0.19/download\"],\n strip_prefix = \"winsafe-0.0.19\",\n build_file = Label(\"@crates_io//crates_io:BUILD.winsafe-0.0.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-bindgen-0.51.0\",\n sha256 = \"d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-0.51.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-bindgen-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-bindgen-core-0.51.0\",\n sha256 = \"ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-core/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-core-0.51.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-bindgen-core-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-bindgen-rust-0.51.0\",\n sha256 = \"b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-0.51.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-bindgen-rust-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-bindgen-rust-macro-0.51.0\",\n sha256 = \"0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-bindgen-rust-macro/0.51.0/download\"],\n strip_prefix = \"wit-bindgen-rust-macro-0.51.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-bindgen-rust-macro-0.51.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-component-0.244.0\",\n sha256 = \"9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-component/0.244.0/download\"],\n strip_prefix = \"wit-component-0.244.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-component-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__wit-parser-0.244.0\",\n sha256 = \"ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wit-parser/0.244.0/download\"],\n strip_prefix = \"wit-parser-0.244.0\",\n build_file = Label(\"@crates_io//crates_io:BUILD.wit-parser-0.244.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__writeable-0.6.2\",\n sha256 = \"9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/writeable/0.6.2/download\"],\n strip_prefix = \"writeable-0.6.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.writeable-0.6.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__xml5ever-0.18.1\",\n sha256 = \"9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/xml5ever/0.18.1/download\"],\n strip_prefix = \"xml5ever-0.18.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.xml5ever-0.18.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__yoke-0.8.1\",\n sha256 = \"72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yoke/0.8.1/download\"],\n strip_prefix = \"yoke-0.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.yoke-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__yoke-derive-0.8.1\",\n sha256 = \"b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/yoke-derive/0.8.1/download\"],\n strip_prefix = \"yoke-derive-0.8.1\",\n build_file = Label(\"@crates_io//crates_io:BUILD.yoke-derive-0.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerocopy-0.8.42\",\n sha256 = \"f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.42/download\"],\n strip_prefix = \"zerocopy-0.8.42\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerocopy-0.8.42.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerocopy-derive-0.8.42\",\n sha256 = \"7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.42/download\"],\n strip_prefix = \"zerocopy-derive-0.8.42\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerocopy-derive-0.8.42.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerofrom-0.1.6\",\n sha256 = \"50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerofrom/0.1.6/download\"],\n strip_prefix = \"zerofrom-0.1.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerofrom-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerofrom-derive-0.1.6\",\n sha256 = \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerofrom-derive/0.1.6/download\"],\n strip_prefix = \"zerofrom-derive-0.1.6\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerofrom-derive-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zeroize-1.8.2\",\n sha256 = \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize/1.8.2/download\"],\n strip_prefix = \"zeroize-1.8.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zeroize-1.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerotrie-0.2.3\",\n sha256 = \"2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerotrie/0.2.3/download\"],\n strip_prefix = \"zerotrie-0.2.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerotrie-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerovec-0.11.5\",\n sha256 = \"6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerovec/0.11.5/download\"],\n strip_prefix = \"zerovec-0.11.5\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerovec-0.11.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zerovec-derive-0.11.2\",\n sha256 = \"eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerovec-derive/0.11.2/download\"],\n strip_prefix = \"zerovec-derive-0.11.2\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zerovec-derive-0.11.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zmij-1.0.21\",\n sha256 = \"b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zmij/1.0.21/download\"],\n strip_prefix = \"zmij-1.0.21\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zmij-1.0.21.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zstd-0.13.3\",\n sha256 = \"e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zstd/0.13.3/download\"],\n strip_prefix = \"zstd-0.13.3\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zstd-0.13.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zstd-safe-7.2.4\",\n sha256 = \"8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zstd-safe/7.2.4/download\"],\n strip_prefix = \"zstd-safe-7.2.4\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zstd-safe-7.2.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates_io__zstd-sys-2.0.16-zstd.1.5.7\",\n sha256 = \"91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zstd-sys/2.0.16+zstd.1.5.7/download\"],\n strip_prefix = \"zstd-sys-2.0.16+zstd.1.5.7\",\n build_file = Label(\"@crates_io//crates_io:BUILD.zstd-sys-2.0.16+zstd.1.5.7.bazel\"),\n )\n\n return [\n struct(repo=\"crates_io__ahash-0.8.12\", is_dev_dep = False),\n struct(repo=\"crates_io__aho-corasick-1.1.4\", is_dev_dep = False),\n struct(repo=\"crates_io__async-trait-0.1.89\", is_dev_dep = False),\n struct(repo=\"crates_io__auto_encoder-0.1.10\", is_dev_dep = False),\n struct(repo=\"crates_io__base64-0.22.1\", is_dev_dep = False),\n struct(repo=\"crates_io__bytes-1.11.1\", is_dev_dep = False),\n struct(repo=\"crates_io__case_insensitive_string-0.2.10\", is_dev_dep = False),\n struct(repo=\"crates_io__chromey-2.40.0\", is_dev_dep = False),\n struct(repo=\"crates_io__clap-4.6.0\", is_dev_dep = False),\n struct(repo=\"crates_io__cookie-0.18.1\", is_dev_dep = False),\n struct(repo=\"crates_io__criterion-0.8.2\", is_dev_dep = False),\n struct(repo=\"crates_io__dashmap-6.1.0\", is_dev_dep = False),\n struct(repo=\"crates_io__env_logger-0.11.9\", is_dev_dep = False),\n struct(repo=\"crates_io__failure_derive-0.1.8\", is_dev_dep = False),\n struct(repo=\"crates_io__fastrand-2.3.0\", is_dev_dep = False),\n struct(repo=\"crates_io__flexbuffers-25.12.19\", is_dev_dep = False),\n struct(repo=\"crates_io__h2-0.4.13\", is_dev_dep = False),\n struct(repo=\"crates_io__hashbrown-0.16.1\", is_dev_dep = False),\n struct(repo=\"crates_io__home-0.5.12\", is_dev_dep = False),\n struct(repo=\"crates_io__io-uring-0.7.11\", is_dev_dep = False),\n struct(repo=\"crates_io__lazy_static-1.5.0\", is_dev_dep = False),\n struct(repo=\"crates_io__libc-0.2.183\", is_dev_dep = False),\n struct(repo=\"crates_io__llm_models_spider-0.1.42\", is_dev_dep = False),\n struct(repo=\"crates_io__log-0.4.29\", is_dev_dep = False),\n struct(repo=\"crates_io__lol_html-2.7.2\", is_dev_dep = False),\n struct(repo=\"crates_io__num_cpus-1.17.0\", is_dev_dep = False),\n struct(repo=\"crates_io__parking_lot-0.12.5\", is_dev_dep = False),\n struct(repo=\"crates_io__percent-encoding-2.3.2\", is_dev_dep = False),\n struct(repo=\"crates_io__phf-0.11.3\", is_dev_dep = False),\n struct(repo=\"crates_io__pin-project-lite-0.2.17\", is_dev_dep = False),\n struct(repo=\"crates_io__quick-xml-0.39.2\", is_dev_dep = False),\n struct(repo=\"crates_io__quote-1.0.45\", is_dev_dep = False),\n struct(repo=\"crates_io__rand-0.9.2\", is_dev_dep = False),\n struct(repo=\"crates_io__regex-1.12.3\", is_dev_dep = False),\n struct(repo=\"crates_io__reqwest-0.13.2\", is_dev_dep = False),\n struct(repo=\"crates_io__serde-1.0.228\", is_dev_dep = False),\n struct(repo=\"crates_io__serde_json-1.0.149\", is_dev_dep = False),\n struct(repo=\"crates_io__serde_regex-1.1.0\", is_dev_dep = False),\n struct(repo=\"crates_io__smallvec-1.15.1\", is_dev_dep = False),\n struct(repo=\"crates_io__spider_fingerprint-2.38.1\", is_dev_dep = False),\n struct(repo=\"crates_io__spider_scraper-0.1.2\", is_dev_dep = False),\n struct(repo=\"crates_io__spider_transformations-2.37.115\", is_dev_dep = False),\n struct(repo=\"crates_io__spider_utils-2.47.18\", is_dev_dep = False),\n struct(repo=\"crates_io__sqlx-0.8.6\", is_dev_dep = False),\n struct(repo=\"crates_io__statrs-0.18.0\", is_dev_dep = False),\n struct(repo=\"crates_io__string-interner-0.19.0\", is_dev_dep = False),\n struct(repo=\"crates_io__string_concat-0.0.1\", is_dev_dep = False),\n struct(repo=\"crates_io__strum-0.27.2\", is_dev_dep = False),\n struct(repo=\"crates_io__sxd-document-0.3.2\", is_dev_dep = False),\n struct(repo=\"crates_io__sxd-xpath-0.4.2\", is_dev_dep = False),\n struct(repo=\"crates_io__sysinfo-0.38.4\", is_dev_dep = False),\n struct(repo=\"crates_io__thiserror-2.0.18\", is_dev_dep = False),\n struct(repo=\"crates_io__tokio-1.50.0\", is_dev_dep = False),\n struct(repo=\"crates_io__tokio-stream-0.1.18\", is_dev_dep = False),\n struct(repo=\"crates_io__tower-0.5.3\", is_dev_dep = False),\n struct(repo=\"crates_io__ua_generator-0.5.52\", is_dev_dep = False),\n struct(repo=\"crates_io__unicode-normalization-0.1.25\", is_dev_dep = False),\n struct(repo=\"crates_io__url-2.5.8\", is_dev_dep = False),\n struct(repo=\"crates_io__warp-0.3.7\", is_dev_dep = False),\n struct(repo=\"crates_io__which-6.0.3\", is_dev_dep = False),\n struct(repo = \"crates_io__console-subscriber-0.4.1\", is_dev_dep = True),\n struct(repo = \"crates_io__convert_case-0.6.0\", is_dev_dep = True),\n struct(repo = \"crates_io__dotenvy-0.15.7\", is_dev_dep = True),\n struct(repo = \"crates_io__futures-0.3.32\", is_dev_dep = True),\n struct(repo = \"crates_io__htr-0.5.27\", is_dev_dep = True),\n ]\n" + } + } + }, + "crates_io__adler2-2.0.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/adler2/2.0.1/download" + ], + "strip_prefix": "adler2-2.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"adler2\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=adler2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.1\",\n)\n" + } + }, + "crates_io__ahash-0.8.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ahash/0.8.12/download" + ], + "strip_prefix": "ahash-0.8.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ahash\",\n deps = [\n \"@crates_io__ahash-0.8.12//:build_script_build\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__zerocopy-0.8.42//:zerocopy\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(not(all(target_arch = \"arm\", target_os = \"none\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ahash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.12\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__version_check-0.9.5//:version_check\",\n ],\n edition = \"2018\",\n pkg_name = \"ahash\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ahash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.12\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__aho-corasick-1.1.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aho-corasick/1.1.4/download" + ], + "strip_prefix": "aho-corasick-1.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aho_corasick\",\n deps = [\n \"@crates_io__memchr-2.8.0//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"perf-literal\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aho-corasick\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n" + } + }, + "crates_io__alloc-no-stdlib-2.0.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/alloc-no-stdlib/2.0.4/download" + ], + "strip_prefix": "alloc-no-stdlib-2.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"alloc_no_stdlib\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=alloc-no-stdlib\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.4\",\n)\n" + } + }, + "crates_io__alloc-stdlib-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/alloc-stdlib/0.2.2/download" + ], + "strip_prefix": "alloc-stdlib-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"alloc_stdlib\",\n deps = [\n \"@crates_io__alloc-no-stdlib-2.0.4//:alloc_no_stdlib\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=alloc-stdlib\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__alloca-0.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/alloca/0.4.0/download" + ], + "strip_prefix": "alloca-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"alloca\",\n deps = [\n \"@crates_io__alloca-0.4.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=alloca\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n ],\n edition = \"2021\",\n pkg_name = \"alloca\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=alloca\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__allocator-api2-0.2.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/allocator-api2/0.2.21/download" + ], + "strip_prefix": "allocator-api2-0.2.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"allocator_api2\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=allocator-api2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.21\",\n)\n" + } + }, + "crates_io__anes-0.1.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anes/0.1.6/download" + ], + "strip_prefix": "anes-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anes\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "crates_io__anstream-0.6.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstream/0.6.21/download" + ], + "strip_prefix": "anstream-0.6.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstream\",\n deps = [\n \"@crates_io__anstyle-1.0.14//:anstyle\",\n \"@crates_io__anstyle-parse-0.2.7//:anstyle_parse\",\n \"@crates_io__anstyle-query-1.1.5//:anstyle_query\",\n \"@crates_io__colorchoice-1.0.5//:colorchoice\",\n \"@crates_io__is_terminal_polyfill-1.70.2//:is_terminal_polyfill\",\n \"@crates_io__utf8parse-0.2.2//:utf8parse\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__anstyle-wincon-3.0.11//:anstyle_wincon\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auto\",\n \"wincon\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.21\",\n)\n" + } + }, + "crates_io__anstream-1.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstream/1.0.0/download" + ], + "strip_prefix": "anstream-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstream\",\n deps = [\n \"@crates_io__anstyle-1.0.14//:anstyle\",\n \"@crates_io__anstyle-parse-1.0.0//:anstyle_parse\",\n \"@crates_io__anstyle-query-1.1.5//:anstyle_query\",\n \"@crates_io__colorchoice-1.0.5//:colorchoice\",\n \"@crates_io__is_terminal_polyfill-1.70.2//:is_terminal_polyfill\",\n \"@crates_io__utf8parse-0.2.2//:utf8parse\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__anstyle-wincon-3.0.11//:anstyle_wincon\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auto\",\n \"default\",\n \"wincon\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n" + } + }, + "crates_io__anstyle-1.0.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle/1.0.14/download" + ], + "strip_prefix": "anstyle-1.0.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.14\",\n)\n" + } + }, + "crates_io__anstyle-parse-0.2.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-parse/0.2.7/download" + ], + "strip_prefix": "anstyle-parse-0.2.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_parse\",\n deps = [\n \"@crates_io__utf8parse-0.2.2//:utf8parse\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"utf8\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-parse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.7\",\n)\n" + } + }, + "crates_io__anstyle-parse-1.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-parse/1.0.0/download" + ], + "strip_prefix": "anstyle-parse-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_parse\",\n deps = [\n \"@crates_io__utf8parse-0.2.2//:utf8parse\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"utf8\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-parse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n" + } + }, + "crates_io__anstyle-query-1.1.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-query/1.1.5/download" + ], + "strip_prefix": "anstyle-query-1.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_query\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-query\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.5\",\n)\n" + } + }, + "crates_io__anstyle-wincon-3.0.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anstyle-wincon/3.0.11/download" + ], + "strip_prefix": "anstyle-wincon-3.0.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anstyle_wincon\",\n deps = [\n \"@crates_io__anstyle-1.0.14//:anstyle\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__once_cell_polyfill-1.70.2//:once_cell_polyfill\", # cfg(windows)\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anstyle-wincon\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.0.11\",\n)\n" + } + }, + "crates_io__anyhow-1.0.102": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/anyhow/1.0.102/download" + ], + "strip_prefix": "anyhow-1.0.102", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"anyhow\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anyhow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.102\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"anyhow\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=anyhow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.102\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__approx-0.5.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/approx/0.5.1/download" + ], + "strip_prefix": "approx-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"approx\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=approx\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "crates_io__async-compression-0.4.41": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/async-compression/0.4.41/download" + ], + "strip_prefix": "async-compression-0.4.41", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"async_compression\",\n deps = [\n \"@crates_io__compression-codecs-0.4.37//:compression_codecs\",\n \"@crates_io__compression-core-0.4.31//:compression_core\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"brotli\",\n \"gzip\",\n \"tokio\",\n \"zlib\",\n \"zstd\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=async-compression\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.41\",\n)\n" + } + }, + "crates_io__async-stream-0.3.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/async-stream/0.3.6/download" + ], + "strip_prefix": "async-stream-0.3.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"async_stream\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n ],\n proc_macro_deps = [\n \"@crates_io__async-stream-impl-0.3.6//:async_stream_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=async-stream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.6\",\n)\n" + } + }, + "crates_io__async-stream-impl-0.3.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/async-stream-impl/0.3.6/download" + ], + "strip_prefix": "async-stream-impl-0.3.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"async_stream_impl\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=async-stream-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.6\",\n)\n" + } + }, + "crates_io__async-trait-0.1.89": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/async-trait/0.1.89/download" + ], + "strip_prefix": "async-trait-0.1.89", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"async_trait\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=async-trait\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.89\",\n)\n" + } + }, + "crates_io__atoi-2.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/atoi/2.0.0/download" + ], + "strip_prefix": "atoi-2.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"atoi\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=atoi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.0\",\n)\n" + } + }, + "crates_io__atomic-waker-1.1.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/atomic-waker/1.1.2/download" + ], + "strip_prefix": "atomic-waker-1.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"atomic_waker\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=atomic-waker\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.2\",\n)\n" + } + }, + "crates_io__auto_encoder-0.1.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3f6364e11e0270035ec392151a54f1476e6b3612ef9f4fe09d35e72a8cebcb65", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/auto_encoder/0.1.10/download" + ], + "strip_prefix": "auto_encoder-0.1.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"auto_encoder\",\n deps = [\n \"@crates_io__chardetng-0.1.17//:chardetng\",\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=auto_encoder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.10\",\n)\n" + } + }, + "crates_io__autocfg-1.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/autocfg/1.5.0/download" + ], + "strip_prefix": "autocfg-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"autocfg\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=autocfg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "crates_io__aws-lc-rs-1.16.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aws-lc-rs/1.16.1/download" + ], + "strip_prefix": "aws-lc-rs-1.16.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aws_lc_rs\",\n deps = [\n \"@crates_io__aws-lc-rs-1.16.1//:build_script_build\",\n \"@crates_io__aws-lc-sys-0.38.0//:aws_lc_sys\",\n \"@crates_io__zeroize-1.8.2//:zeroize\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-sys\",\n \"prebuilt-nasm\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.16.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-sys\",\n \"prebuilt-nasm\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__aws-lc-sys-0.38.0//:aws_lc_sys\",\n ],\n edition = \"2021\",\n links = \"aws_lc_rs_1_16_1_sys\",\n pkg_name = \"aws-lc-rs\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.16.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__aws-lc-sys-0.38.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/aws-lc-sys/0.38.0/download" + ], + "strip_prefix": "aws-lc-sys-0.38.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aws_lc_sys\",\n deps = [\n \"@crates_io__aws-lc-sys-0.38.0//:build_script_main\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"prebuilt-nasm\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.38.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n build_script_env = {\n \"CFLAGS\": \"-O0\",\n \"CRATE_CC_NO_DEFAULTS\": \"1\",\n \"OPT_LEVEL\": \"0\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"prebuilt-nasm\",\n ],\n crate_name = \"build_script_main\",\n crate_root = \"builder/main.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n \"@crates_io__cmake-0.1.57//:cmake\",\n \"@crates_io__dunce-1.0.5//:dunce\",\n \"@crates_io__fs_extra-1.3.0//:fs_extra\",\n ],\n edition = \"2021\",\n links = \"aws_lc_0_38_0\",\n pkg_name = \"aws-lc-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aws-lc-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.38.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_main\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__axum-0.7.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/axum/0.7.9/download" + ], + "strip_prefix": "axum-0.7.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"axum\",\n deps = [\n \"@crates_io__axum-core-0.4.5//:axum_core\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__http-body-util-0.1.3//:http_body_util\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__matchit-0.7.3//:matchit\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@crates_io__tower-0.5.3//:tower\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n ],\n proc_macro_deps = [\n \"@crates_io__async-trait-0.1.89//:async_trait\",\n \"@crates_io__rustversion-1.0.22//:rustversion\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=axum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.9\",\n)\n" + } + }, + "crates_io__axum-core-0.4.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/axum-core/0.4.5/download" + ], + "strip_prefix": "axum-core-0.4.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"axum_core\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__http-body-util-0.1.3//:http_body_util\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n ],\n proc_macro_deps = [\n \"@crates_io__async-trait-0.1.89//:async_trait\",\n \"@crates_io__rustversion-1.0.22//:rustversion\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=axum-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.5\",\n)\n" + } + }, + "crates_io__base64-0.21.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/base64/0.21.7/download" + ], + "strip_prefix": "base64-0.21.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"base64\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=base64\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.21.7\",\n)\n" + } + }, + "crates_io__base64-0.22.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/base64/0.22.1/download" + ], + "strip_prefix": "base64-0.22.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"base64\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=base64\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.22.1\",\n)\n" + } + }, + "crates_io__base64ct-1.8.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/base64ct/1.8.3/download" + ], + "strip_prefix": "base64ct-1.8.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"base64ct\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=base64ct\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.3\",\n)\n" + } + }, + "crates_io__bitflags-1.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitflags/1.3.2/download" + ], + "strip_prefix": "bitflags-1.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitflags\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitflags\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.2\",\n)\n" + } + }, + "crates_io__bitflags-2.11.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bitflags/2.11.0/download" + ], + "strip_prefix": "bitflags-2.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bitflags\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"std\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"std\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"std\", # wasm32-wasip1\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"std\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"std\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bitflags\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.11.0\",\n)\n" + } + }, + "crates_io__block-buffer-0.10.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/block-buffer/0.10.4/download" + ], + "strip_prefix": "block-buffer-0.10.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"block_buffer\",\n deps = [\n \"@crates_io__generic-array-0.14.7//:generic_array\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=block-buffer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.4\",\n)\n" + } + }, + "crates_io__brotli-8.0.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/brotli/8.0.2/download" + ], + "strip_prefix": "brotli-8.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"brotli\",\n deps = [\n \"@crates_io__alloc-no-stdlib-2.0.4//:alloc_no_stdlib\",\n \"@crates_io__alloc-stdlib-0.2.2//:alloc_stdlib\",\n \"@crates_io__brotli-decompressor-5.0.0//:brotli_decompressor\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc-stdlib\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=brotli\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"8.0.2\",\n)\n" + } + }, + "crates_io__brotli-decompressor-4.0.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/brotli-decompressor/4.0.3/download" + ], + "strip_prefix": "brotli-decompressor-4.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"brotli_decompressor\",\n deps = [\n \"@crates_io__alloc-no-stdlib-2.0.4//:alloc_no_stdlib\",\n \"@crates_io__alloc-stdlib-0.2.2//:alloc_stdlib\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc-stdlib\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=brotli-decompressor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.0.3\",\n)\n" + } + }, + "crates_io__brotli-decompressor-5.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/brotli-decompressor/5.0.0/download" + ], + "strip_prefix": "brotli-decompressor-5.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"brotli_decompressor\",\n deps = [\n \"@crates_io__alloc-no-stdlib-2.0.4//:alloc_no_stdlib\",\n \"@crates_io__alloc-stdlib-0.2.2//:alloc_stdlib\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc-stdlib\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=brotli-decompressor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"5.0.0\",\n)\n" + } + }, + "crates_io__bumpalo-3.20.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bumpalo/3.20.2/download" + ], + "strip_prefix": "bumpalo-3.20.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bumpalo\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bumpalo\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.20.2\",\n)\n" + } + }, + "crates_io__bytemuck-1.25.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bytemuck/1.25.0/download" + ], + "strip_prefix": "bytemuck-1.25.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bytemuck\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bytemuck\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.25.0\",\n)\n" + } + }, + "crates_io__byteorder-1.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/byteorder/1.5.0/download" + ], + "strip_prefix": "byteorder-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"byteorder\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=byteorder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "crates_io__bytes-1.11.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/bytes/1.11.1/download" + ], + "strip_prefix": "bytes-1.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"bytes\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=bytes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.11.1\",\n)\n" + } + }, + "crates_io__case_insensitive_string-0.2.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ca6260a8cc32da29c626ce456aad5c579c3a36294722f00cccceeb64968820f7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/case_insensitive_string/0.2.10/download" + ], + "strip_prefix": "case_insensitive_string-0.2.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"case_insensitive_string\",\n deps = [\n \"@crates_io__compact_str-0.8.1//:compact_str\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compact\",\n \"default\",\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=case_insensitive_string\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.10\",\n)\n" + } + }, + "crates_io__cast-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cast/0.3.0/download" + ], + "strip_prefix": "cast-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cast\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cast\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "crates_io__castaway-0.2.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/castaway/0.2.4/download" + ], + "strip_prefix": "castaway-0.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"castaway\",\n proc_macro_deps = [\n \"@crates_io__rustversion-1.0.22//:rustversion\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=castaway\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.4\",\n)\n" + } + }, + "crates_io__cc-1.2.57": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cc/1.2.57/download" + ], + "strip_prefix": "cc-1.2.57", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cc\",\n deps = [\n \"@crates_io__find-msvc-tools-0.1.9//:find_msvc_tools\",\n \"@crates_io__jobserver-0.1.34//:jobserver\",\n \"@crates_io__shlex-1.3.0//:shlex\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"parallel\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.57\",\n)\n" + } + }, + "crates_io__cesu8-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cesu8/1.1.0/download" + ], + "strip_prefix": "cesu8-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cesu8\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cesu8\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__cfg-if-1.0.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cfg-if/1.0.4/download" + ], + "strip_prefix": "cfg-if-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cfg_if\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cfg-if\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "crates_io__cfg_aliases-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cfg_aliases/0.2.1/download" + ], + "strip_prefix": "cfg_aliases-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cfg_aliases\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cfg_aliases\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__chardetng-0.1.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/chardetng/0.1.17/download" + ], + "strip_prefix": "chardetng-0.1.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chardetng\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=chardetng\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.17\",\n)\n" + } + }, + "crates_io__chromey-2.40.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "183679faab96df2da020aa6e56eb9302cae67ce21cc00adbba60254c6d23a262", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/chromey/2.40.0/download" + ], + "strip_prefix": "chromey-2.40.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chromiumoxide\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__case_insensitive_string-0.2.10//:case_insensitive_string\",\n \"@crates_io__dunce-1.0.5//:dunce\",\n \"@crates_io__fnv-1.0.7//:fnv\",\n \"@crates_io__futures-0.3.32//:futures\",\n \"@crates_io__futures-timer-3.0.3//:futures_timer\",\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__rand-0.9.2//:rand\",\n \"@crates_io__reqwest-0.13.2//:reqwest\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__spider_chromiumoxide_cdp-0.7.9//:chromiumoxide_cdp\",\n \"@crates_io__spider_chromiumoxide_types-0.7.5//:chromiumoxide_types\",\n \"@crates_io__spider_fingerprint-2.38.1//:spider_fingerprint\",\n \"@crates_io__spider_network_blocker-0.0.58//:spider_network_blocker\",\n \"@crates_io__thiserror-1.0.69//:thiserror\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-tungstenite-0.28.0//:tokio_tungstenite\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__url-2.5.8//:url\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__winreg-0.52.0//:winreg\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bytes\",\n \"stream\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=chromey\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.40.0\",\n)\n" + } + }, + "crates_io__ciborium-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium/0.2.2/download" + ], + "strip_prefix": "ciborium-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium\",\n deps = [\n \"@crates_io__ciborium-io-0.2.2//:ciborium_io\",\n \"@crates_io__ciborium-ll-0.2.2//:ciborium_ll\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__ciborium-io-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium-io/0.2.2/download" + ], + "strip_prefix": "ciborium-io-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium_io\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium-io\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__ciborium-ll-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ciborium-ll/0.2.2/download" + ], + "strip_prefix": "ciborium-ll-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ciborium_ll\",\n deps = [\n \"@crates_io__ciborium-io-0.2.2//:ciborium_io\",\n \"@crates_io__half-2.7.1//:half\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ciborium-ll\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__clap-4.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap/4.6.0/download" + ], + "strip_prefix": "clap-4.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap\",\n deps = [\n \"@crates_io__clap_builder-4.6.0//:clap_builder\",\n ],\n proc_macro_deps = [\n \"@crates_io__clap_derive-4.6.0//:clap_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"color\",\n \"default\",\n \"derive\",\n \"error-context\",\n \"help\",\n \"std\",\n \"suggestions\",\n \"usage\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.6.0\",\n)\n" + } + }, + "crates_io__clap_builder-4.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_builder/4.6.0/download" + ], + "strip_prefix": "clap_builder-4.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap_builder\",\n deps = [\n \"@crates_io__anstream-1.0.0//:anstream\",\n \"@crates_io__anstyle-1.0.14//:anstyle\",\n \"@crates_io__clap_lex-1.1.0//:clap_lex\",\n \"@crates_io__strsim-0.11.1//:strsim\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"color\",\n \"error-context\",\n \"help\",\n \"std\",\n \"suggestions\",\n \"usage\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_builder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.6.0\",\n)\n" + } + }, + "crates_io__clap_derive-4.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_derive/4.6.0/download" + ], + "strip_prefix": "clap_derive-4.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"clap_derive\",\n deps = [\n \"@crates_io__heck-0.5.0//:heck\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.6.0\",\n)\n" + } + }, + "crates_io__clap_lex-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/clap_lex/1.1.0/download" + ], + "strip_prefix": "clap_lex-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"clap_lex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=clap_lex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__cmake-0.1.57": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cmake/0.1.57/download" + ], + "strip_prefix": "cmake-0.1.57", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cmake\",\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cmake\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.57\",\n)\n" + } + }, + "crates_io__colorchoice-1.0.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/colorchoice/1.0.5/download" + ], + "strip_prefix": "colorchoice-1.0.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"colorchoice\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=colorchoice\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.5\",\n)\n" + } + }, + "crates_io__combine-4.6.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/combine/4.6.7/download" + ], + "strip_prefix": "combine-4.6.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"combine\",\n deps = [\n \"@crates_io__memchr-2.8.0//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=combine\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"4.6.7\",\n)\n" + } + }, + "crates_io__compact_str-0.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/compact_str/0.8.1/download" + ], + "strip_prefix": "compact_str-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"compact_str\",\n deps = [\n \"@crates_io__castaway-0.2.4//:castaway\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__ryu-1.0.23//:ryu\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__static_assertions-1.1.0//:static_assertions\",\n ],\n proc_macro_deps = [\n \"@crates_io__rustversion-1.0.22//:rustversion\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=compact_str\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "crates_io__compression-codecs-0.4.37": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/compression-codecs/0.4.37/download" + ], + "strip_prefix": "compression-codecs-0.4.37", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"compression_codecs\",\n deps = [\n \"@crates_io__brotli-8.0.2//:brotli\",\n \"@crates_io__compression-core-0.4.31//:compression_core\",\n \"@crates_io__flate2-1.1.9//:flate2\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__zstd-0.13.3//:zstd\",\n \"@crates_io__zstd-safe-7.2.4//:zstd_safe\",\n ],\n aliases = {\n \"@crates_io__zstd-0.13.3//:zstd\": \"libzstd\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"brotli\",\n \"flate2\",\n \"gzip\",\n \"libzstd\",\n \"memchr\",\n \"zlib\",\n \"zstd\",\n \"zstd-safe\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=compression-codecs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.37\",\n)\n" + } + }, + "crates_io__compression-core-0.4.31": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/compression-core/0.4.31/download" + ], + "strip_prefix": "compression-core-0.4.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"compression_core\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=compression-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.31\",\n)\n" + } + }, + "crates_io__concurrent-queue-2.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/concurrent-queue/2.5.0/download" + ], + "strip_prefix": "concurrent-queue-2.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"concurrent_queue\",\n deps = [\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=concurrent-queue\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.5.0\",\n)\n" + } + }, + "crates_io__console-api-0.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/console-api/0.8.1/download" + ], + "strip_prefix": "console-api-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"console_api\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__prost-0.13.5//:prost\",\n \"@crates_io__prost-types-0.13.5//:prost_types\",\n \"@crates_io__tonic-0.12.3//:tonic\",\n \"@crates_io__tracing-core-0.1.36//:tracing_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"transport\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=console-api\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "crates_io__console-subscriber-0.4.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/console-subscriber/0.4.1/download" + ], + "strip_prefix": "console-subscriber-0.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"console_subscriber\",\n deps = [\n \"@crates_io__console-api-0.8.1//:console_api\",\n \"@crates_io__crossbeam-channel-0.5.15//:crossbeam_channel\",\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@crates_io__futures-task-0.3.32//:futures_task\",\n \"@crates_io__hdrhistogram-7.5.4//:hdrhistogram\",\n \"@crates_io__humantime-2.3.0//:humantime\",\n \"@crates_io__hyper-util-0.1.20//:hyper_util\",\n \"@crates_io__prost-0.13.5//:prost\",\n \"@crates_io__prost-types-0.13.5//:prost_types\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__thread_local-1.1.9//:thread_local\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n \"@crates_io__tonic-0.12.3//:tonic\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__tracing-core-0.1.36//:tracing_core\",\n \"@crates_io__tracing-subscriber-0.3.23//:tracing_subscriber\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"env-filter\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=console-subscriber\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.1\",\n)\n" + } + }, + "crates_io__const-oid-0.9.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/const-oid/0.9.6/download" + ], + "strip_prefix": "const-oid-0.9.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"const_oid\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=const-oid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.6\",\n)\n" + } + }, + "crates_io__convert_case-0.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/convert_case/0.5.0/download" + ], + "strip_prefix": "convert_case-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"convert_case\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=convert_case\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "crates_io__convert_case-0.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/convert_case/0.6.0/download" + ], + "strip_prefix": "convert_case-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"convert_case\",\n deps = [\n \"@crates_io__unicode-segmentation-1.12.0//:unicode_segmentation\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=convert_case\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n" + } + }, + "crates_io__convert_case-0.8.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/convert_case/0.8.0/download" + ], + "strip_prefix": "convert_case-0.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"convert_case\",\n deps = [\n \"@crates_io__unicode-segmentation-1.12.0//:unicode_segmentation\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=convert_case\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.0\",\n)\n" + } + }, + "crates_io__cookie-0.18.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cookie/0.18.1/download" + ], + "strip_prefix": "cookie-0.18.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cookie\",\n deps = [\n \"@crates_io__cookie-0.18.1//:build_script_build\",\n \"@crates_io__time-0.3.47//:time\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"percent-encode\", # aarch64-apple-darwin\n \"percent-encoding\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"percent-encode\", # aarch64-unknown-linux-gnu\n \"percent-encoding\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"percent-encode\", # x86_64-pc-windows-msvc\n \"percent-encoding\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"percent-encode\", # x86_64-unknown-linux-gnu\n \"percent-encoding\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"percent-encode\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"percent-encoding\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cookie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.18.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"percent-encode\", # aarch64-apple-darwin\n \"percent-encoding\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"percent-encode\", # aarch64-unknown-linux-gnu\n \"percent-encoding\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"percent-encode\", # x86_64-pc-windows-msvc\n \"percent-encoding\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"percent-encode\", # x86_64-unknown-linux-gnu\n \"percent-encoding\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"percent-encode\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"percent-encoding\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__version_check-0.9.5//:version_check\",\n ],\n edition = \"2018\",\n pkg_name = \"cookie\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cookie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.18.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__cookie_store-0.22.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cookie_store/0.22.1/download" + ], + "strip_prefix": "cookie_store-0.22.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cookie_store\",\n deps = [\n \"@crates_io__cookie-0.18.1//:cookie\",\n \"@crates_io__idna-1.1.0//:idna\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__publicsuffix-2.3.0//:publicsuffix\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__time-0.3.47//:time\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n proc_macro_deps = [\n \"@crates_io__document-features-0.2.12//:document_features\",\n \"@crates_io__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"public_suffix\",\n \"serde\",\n \"serde_json\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cookie_store\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.22.1\",\n)\n" + } + }, + "crates_io__core-foundation-0.9.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/core-foundation/0.9.4/download" + ], + "strip_prefix": "core-foundation-0.9.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"core_foundation\",\n deps = [\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@crates_io__libc-0.2.183//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=core-foundation\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.4\",\n)\n" + } + }, + "crates_io__core-foundation-0.10.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/core-foundation/0.10.1/download" + ], + "strip_prefix": "core-foundation-0.10.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"core_foundation\",\n deps = [\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@crates_io__libc-0.2.183//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=core-foundation\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.1\",\n)\n" + } + }, + "crates_io__core-foundation-sys-0.8.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/core-foundation-sys/0.8.7/download" + ], + "strip_prefix": "core-foundation-sys-0.8.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"core_foundation_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"link\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=core-foundation-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.7\",\n)\n" + } + }, + "crates_io__cpufeatures-0.2.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cpufeatures/0.2.17/download" + ], + "strip_prefix": "cpufeatures-0.2.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cpufeatures\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cpufeatures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.17\",\n)\n" + } + }, + "crates_io__crc-3.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crc/3.4.0/download" + ], + "strip_prefix": "crc-3.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crc\",\n deps = [\n \"@crates_io__crc-catalog-2.4.0//:crc_catalog\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.4.0\",\n)\n" + } + }, + "crates_io__crc-catalog-2.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crc-catalog/2.4.0/download" + ], + "strip_prefix": "crc-catalog-2.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crc_catalog\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crc-catalog\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.4.0\",\n)\n" + } + }, + "crates_io__crc32fast-1.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crc32fast/1.5.0/download" + ], + "strip_prefix": "crc32fast-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crc32fast\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__crc32fast-1.5.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crc32fast\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"crc32fast\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crc32fast\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.5.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__criterion-0.8.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/criterion/0.8.2/download" + ], + "strip_prefix": "criterion-0.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"criterion\",\n deps = [\n \"@crates_io__anes-0.1.6//:anes\",\n \"@crates_io__cast-0.3.0//:cast\",\n \"@crates_io__ciborium-0.2.2//:ciborium\",\n \"@crates_io__clap-4.6.0//:clap\",\n \"@crates_io__criterion-plot-0.8.2//:criterion_plot\",\n \"@crates_io__itertools-0.13.0//:itertools\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__oorandom-11.1.5//:oorandom\",\n \"@crates_io__page_size-0.6.0//:page_size\",\n \"@crates_io__plotters-0.3.7//:plotters\",\n \"@crates_io__rayon-1.11.0//:rayon\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__tinytemplate-1.2.1//:tinytemplate\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__walkdir-2.5.0//:walkdir\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__alloca-0.4.0//:alloca\", # cfg(any(windows, unix))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__alloca-0.4.0//:alloca\", # cfg(any(windows, unix))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__alloca-0.4.0//:alloca\", # cfg(any(windows, unix))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__alloca-0.4.0//:alloca\", # cfg(any(windows, unix))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__alloca-0.4.0//:alloca\", # cfg(any(windows, unix))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async\",\n \"async_tokio\",\n \"cargo_bench_support\",\n \"default\",\n \"html_reports\",\n \"plotters\",\n \"rayon\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=criterion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.2\",\n)\n" + } + }, + "crates_io__criterion-plot-0.8.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/criterion-plot/0.8.2/download" + ], + "strip_prefix": "criterion-plot-0.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"criterion_plot\",\n deps = [\n \"@crates_io__cast-0.3.0//:cast\",\n \"@crates_io__itertools-0.13.0//:itertools\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=criterion-plot\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.2\",\n)\n" + } + }, + "crates_io__crossbeam-channel-0.5.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-channel/0.5.15/download" + ], + "strip_prefix": "crossbeam-channel-0.5.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_channel\",\n deps = [\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-channel\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.15\",\n)\n" + } + }, + "crates_io__crossbeam-deque-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-deque/0.8.6/download" + ], + "strip_prefix": "crossbeam-deque-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_deque\",\n deps = [\n \"@crates_io__crossbeam-epoch-0.9.18//:crossbeam_epoch\",\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-deque\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__crossbeam-epoch-0.9.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-epoch/0.9.18/download" + ], + "strip_prefix": "crossbeam-epoch-0.9.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_epoch\",\n deps = [\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-epoch\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.18\",\n)\n" + } + }, + "crates_io__crossbeam-queue-0.3.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-queue/0.3.12/download" + ], + "strip_prefix": "crossbeam-queue-0.3.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_queue\",\n deps = [\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-queue\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.12\",\n)\n" + } + }, + "crates_io__crossbeam-utils-0.8.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crossbeam-utils/0.8.21/download" + ], + "strip_prefix": "crossbeam-utils-0.8.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crossbeam_utils\",\n deps = [\n \"@crates_io__crossbeam-utils-0.8.21//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.21\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"crossbeam-utils\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crossbeam-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.21\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__crunchy-0.2.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crunchy/0.2.4/download" + ], + "strip_prefix": "crunchy-0.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crunchy\",\n deps = [\n \"@crates_io__crunchy-0.2.4//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crunchy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"crunchy\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crunchy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__crypto-common-0.1.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/crypto-common/0.1.7/download" + ], + "strip_prefix": "crypto-common-0.1.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"crypto_common\",\n deps = [\n \"@crates_io__generic-array-0.14.7//:generic_array\",\n \"@crates_io__typenum-1.19.0//:typenum\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=crypto-common\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.7\",\n)\n" + } + }, + "crates_io__cssparser-0.31.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cssparser/0.31.2/download" + ], + "strip_prefix": "cssparser-0.31.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cssparser\",\n deps = [\n \"@crates_io__dtoa-short-0.3.5//:dtoa_short\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ],\n proc_macro_deps = [\n \"@crates_io__cssparser-macros-0.6.1//:cssparser_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cssparser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.31.2\",\n)\n" + } + }, + "crates_io__cssparser-0.36.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cssparser/0.36.0/download" + ], + "strip_prefix": "cssparser-0.36.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"cssparser\",\n deps = [\n \"@crates_io__dtoa-short-0.3.5//:dtoa_short\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__phf-0.13.1//:phf\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ],\n proc_macro_deps = [\n \"@crates_io__cssparser-macros-0.6.1//:cssparser_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cssparser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.36.0\",\n)\n" + } + }, + "crates_io__cssparser-macros-0.6.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/cssparser-macros/0.6.1/download" + ], + "strip_prefix": "cssparser-macros-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"cssparser_macros\",\n deps = [\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=cssparser-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "crates_io__dashmap-6.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dashmap/6.1.0/download" + ], + "strip_prefix": "dashmap-6.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dashmap\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@crates_io__hashbrown-0.14.5//:hashbrown\",\n \"@crates_io__lock_api-0.4.14//:lock_api\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__parking_lot_core-0.9.12//:parking_lot_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dashmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"6.1.0\",\n)\n" + } + }, + "crates_io__data-encoding-2.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/data-encoding/2.10.0/download" + ], + "strip_prefix": "data-encoding-2.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"data_encoding\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=data-encoding\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.10.0\",\n)\n" + } + }, + "crates_io__der-0.7.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/der/0.7.10/download" + ], + "strip_prefix": "der-0.7.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"der\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=der\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.10\",\n)\n" + } + }, + "crates_io__deranged-0.5.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/deranged/0.5.8/download" + ], + "strip_prefix": "deranged-0.5.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"deranged\",\n deps = [\n \"@crates_io__powerfmt-0.2.0//:powerfmt\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"powerfmt\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=deranged\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.8\",\n)\n" + } + }, + "crates_io__derive_more-0.99.20": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/derive_more/0.99.20/download" + ], + "strip_prefix": "derive_more-0.99.20", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"derive_more\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"add\",\n \"add_assign\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=derive_more\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.99.20\",\n)\n" + } + }, + "crates_io__derive_more-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/derive_more/2.1.1/download" + ], + "strip_prefix": "derive_more-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"derive_more\",\n proc_macro_deps = [\n \"@crates_io__derive_more-impl-2.1.1//:derive_more_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"add\",\n \"add_assign\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=derive_more\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__derive_more-impl-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/derive_more-impl/2.1.1/download" + ], + "strip_prefix": "derive_more-impl-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"derive_more_impl\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"add\",\n \"add_assign\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=derive_more-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__digest-0.10.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/digest/0.10.7/download" + ], + "strip_prefix": "digest-0.10.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"digest\",\n deps = [\n \"@crates_io__block-buffer-0.10.4//:block_buffer\",\n \"@crates_io__crypto-common-0.1.7//:crypto_common\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"block-buffer\",\n \"core-api\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=digest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.7\",\n)\n" + } + }, + "crates_io__displaydoc-0.2.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/displaydoc/0.2.5/download" + ], + "strip_prefix": "displaydoc-0.2.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"displaydoc\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=displaydoc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.5\",\n)\n" + } + }, + "crates_io__document-features-0.2.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/document-features/0.2.12/download" + ], + "strip_prefix": "document-features-0.2.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"document_features\",\n deps = [\n \"@crates_io__litrs-1.0.0//:litrs\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=document-features\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.12\",\n)\n" + } + }, + "crates_io__dotenvy-0.15.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dotenvy/0.15.7/download" + ], + "strip_prefix": "dotenvy-0.15.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dotenvy\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dotenvy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.15.7\",\n)\n" + } + }, + "crates_io__dtoa-1.0.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dtoa/1.0.11/download" + ], + "strip_prefix": "dtoa-1.0.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dtoa\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dtoa\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.11\",\n)\n" + } + }, + "crates_io__dtoa-short-0.3.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dtoa-short/0.3.5/download" + ], + "strip_prefix": "dtoa-short-0.3.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dtoa_short\",\n deps = [\n \"@crates_io__dtoa-1.0.11//:dtoa\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dtoa-short\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.5\",\n)\n" + } + }, + "crates_io__dunce-1.0.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/dunce/1.0.5/download" + ], + "strip_prefix": "dunce-1.0.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"dunce\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=dunce\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.5\",\n)\n" + } + }, + "crates_io__ego-tree-0.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ego-tree/0.10.0/download" + ], + "strip_prefix": "ego-tree-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ego_tree\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ego-tree\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "crates_io__either-1.15.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/either/1.15.0/download" + ], + "strip_prefix": "either-1.15.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"either\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"std\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=either\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.15.0\",\n)\n" + } + }, + "crates_io__encoding_rs-0.8.35": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/encoding_rs/0.8.35/download" + ], + "strip_prefix": "encoding_rs-0.8.35", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"encoding_rs\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=encoding_rs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.35\",\n)\n" + } + }, + "crates_io__env_filter-1.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/env_filter/1.0.0/download" + ], + "strip_prefix": "env_filter-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"env_filter\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__regex-1.12.3//:regex\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"regex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=env_filter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n" + } + }, + "crates_io__env_logger-0.11.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/env_logger/0.11.9/download" + ], + "strip_prefix": "env_logger-0.11.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"env_logger\",\n deps = [\n \"@crates_io__anstream-0.6.21//:anstream\",\n \"@crates_io__anstyle-1.0.14//:anstyle\",\n \"@crates_io__env_filter-1.0.0//:env_filter\",\n \"@crates_io__jiff-0.2.23//:jiff\",\n \"@crates_io__log-0.4.29//:log\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"auto-color\",\n \"color\",\n \"default\",\n \"humantime\",\n \"regex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=env_logger\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.9\",\n)\n" + } + }, + "crates_io__equivalent-1.0.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/equivalent/1.0.2/download" + ], + "strip_prefix": "equivalent-1.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"equivalent\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=equivalent\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2\",\n)\n" + } + }, + "crates_io__errno-0.3.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/errno/0.3.14/download" + ], + "strip_prefix": "errno-0.3.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"errno\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=errno\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.14\",\n)\n" + } + }, + "crates_io__etcetera-0.8.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/etcetera/0.8.0/download" + ], + "strip_prefix": "etcetera-0.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"etcetera\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__home-0.5.12//:home\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.48.0//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=etcetera\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.0\",\n)\n" + } + }, + "crates_io__event-listener-5.4.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/event-listener/5.4.1/download" + ], + "strip_prefix": "event-listener-5.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"event_listener\",\n deps = [\n \"@crates_io__concurrent-queue-2.5.0//:concurrent_queue\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__parking-2.2.1//:parking\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__parking-2.2.1//:parking\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__parking-2.2.1//:parking\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__parking-2.2.1//:parking\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__parking-2.2.1//:parking\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"parking\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=event-listener\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"5.4.1\",\n)\n" + } + }, + "crates_io__failure_derive-0.1.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/failure_derive/0.1.8/download" + ], + "strip_prefix": "failure_derive-0.1.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"failure_derive\",\n deps = [\n \"@crates_io__failure_derive-0.1.8//:build_script_build\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-1.0.109//:syn\",\n \"@crates_io__synstructure-0.12.6//:synstructure\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=failure_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.8\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"failure_derive\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=failure_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.1.8\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__fast_html2md-0.0.58": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "af3a0122fee1bcf6bb9f3d73782e911cce69d95b76a5e29e930af92cd4a8e4e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fast_html2md/0.0.58/download" + ], + "strip_prefix": "fast_html2md-0.0.58", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"html2md\",\n deps = [\n \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__lol_html-2.7.2//:lol_html\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"ignore_cookies\",\n \"rewriter\",\n \"stream\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fast_html2md\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.58\",\n)\n" + } + }, + "crates_io__fast_html5ever-0.26.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "edb09004fadff4b73e882edeb42527d3789009ca821467169168867eeb71681d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fast_html5ever/0.26.6/download" + ], + "strip_prefix": "fast_html5ever-0.26.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fast_html5ever\",\n deps = [\n \"@crates_io__fast_html5ever-0.26.6//:build_script_build\",\n \"@crates_io__fast_markup5ever-0.11.1//:fast_markup5ever\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__mac-0.1.1//:mac\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fast_html5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-1.0.109//:syn\",\n ],\n edition = \"2018\",\n pkg_name = \"fast_html5ever\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fast_html5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.26.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__fast_markup5ever-0.11.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f899f3b7e9bf005393dd90318d7e721e0ecd230073b675a6737ed502ecff557c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fast_markup5ever/0.11.1/download" + ], + "strip_prefix": "fast_markup5ever-0.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fast_markup5ever\",\n deps = [\n \"@crates_io__fast_markup5ever-0.11.1//:build_script_build\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__string_cache-0.8.9//:string_cache\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fast_markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n \"@crates_io__string_cache_codegen-0.5.4//:string_cache_codegen\",\n ],\n edition = \"2018\",\n pkg_name = \"fast_markup5ever\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fast_markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.11.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__fastrand-2.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fastrand/2.3.0/download" + ], + "strip_prefix": "fastrand-2.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fastrand\",\n deps = select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__getrandom-0.2.17//:getrandom\", # wasm32-unknown-unknown\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"getrandom\", # wasm32-unknown-unknown\n \"js\", # wasm32-unknown-unknown\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"getrandom\", # wasm32-wasip1\n \"js\", # wasm32-wasip1\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fastrand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.0\",\n)\n" + } + }, + "crates_io__find-msvc-tools-0.1.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/find-msvc-tools/0.1.9/download" + ], + "strip_prefix": "find-msvc-tools-0.1.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"find_msvc_tools\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=find-msvc-tools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.9\",\n)\n" + } + }, + "crates_io__flate2-1.1.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/flate2/1.1.9/download" + ], + "strip_prefix": "flate2-1.1.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"flate2\",\n deps = [\n \"@crates_io__crc32fast-1.5.0//:crc32fast\",\n \"@crates_io__miniz_oxide-0.8.9//:miniz_oxide\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"any_impl\",\n \"default\",\n \"miniz_oxide\",\n \"rust_backend\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=flate2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.9\",\n)\n" + } + }, + "crates_io__flexbuffers-25.12.19": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8bc752b3d049e0705749b9999d0b130d6cf62935bc7762fd3bdb7636047abe43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/flexbuffers/25.12.19/download" + ], + "strip_prefix": "flexbuffers-25.12.19", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"flexbuffers\",\n deps = [\n \"@crates_io__bitflags-1.3.2//:bitflags\",\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n \"@crates_io__num_enum-0.5.11//:num_enum\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n proc_macro_deps = [\n \"@crates_io__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=flexbuffers\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"25.12.19\",\n)\n" + } + }, + "crates_io__flume-0.11.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/flume/0.11.1/download" + ], + "strip_prefix": "flume-0.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"flume\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__spin-0.9.8//:spin\",\n ],\n aliases = {\n \"@crates_io__spin-0.9.8//:spin\": \"spin1\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async\",\n \"futures-core\",\n \"futures-sink\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=flume\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1\",\n)\n" + } + }, + "crates_io__fnv-1.0.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fnv/1.0.7/download" + ], + "strip_prefix": "fnv-1.0.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fnv\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fnv\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.7\",\n)\n" + } + }, + "crates_io__foldhash-0.1.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foldhash/0.1.5/download" + ], + "strip_prefix": "foldhash-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foldhash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foldhash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "crates_io__foldhash-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foldhash/0.2.0/download" + ], + "strip_prefix": "foldhash-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foldhash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foldhash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "crates_io__foreign-types-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foreign-types/0.3.2/download" + ], + "strip_prefix": "foreign-types-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foreign_types\",\n deps = [\n \"@crates_io__foreign-types-shared-0.1.1//:foreign_types_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foreign-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__foreign-types-shared-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/foreign-types-shared/0.1.1/download" + ], + "strip_prefix": "foreign-types-shared-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"foreign_types_shared\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=foreign-types-shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__form_urlencoded-1.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/form_urlencoded/1.2.2/download" + ], + "strip_prefix": "form_urlencoded-1.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"form_urlencoded\",\n deps = [\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=form_urlencoded\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.2\",\n)\n" + } + }, + "crates_io__fs_extra-1.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fs_extra/1.3.0/download" + ], + "strip_prefix": "fs_extra-1.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fs_extra\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fs_extra\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.0\",\n)\n" + } + }, + "crates_io__futf-0.1.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futf/0.1.5/download" + ], + "strip_prefix": "futf-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futf\",\n deps = [\n \"@crates_io__mac-0.1.1//:mac\",\n \"@crates_io__new_debug_unreachable-1.0.6//:debug_unreachable\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "crates_io__futures-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures/0.3.32/download" + ], + "strip_prefix": "futures-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures\",\n deps = [\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-executor-0.3.32//:futures_executor\",\n \"@crates_io__futures-io-0.3.32//:futures_io\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__futures-task-0.3.32//:futures_task\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"async-await\",\n \"default\",\n \"executor\",\n \"futures-executor\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-channel-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-channel/0.3.32/download" + ], + "strip_prefix": "futures-channel-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_channel\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"futures-sink\",\n \"sink\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-channel\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-core-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-core/0.3.32/download" + ], + "strip_prefix": "futures-core-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_core\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-executor-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-executor/0.3.32/download" + ], + "strip_prefix": "futures-executor-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_executor\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-task-0.3.32//:futures_task\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-executor\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-intrusive-0.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-intrusive/0.5.0/download" + ], + "strip_prefix": "futures-intrusive-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_intrusive\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__lock_api-0.4.14//:lock_api\",\n \"@crates_io__parking_lot-0.12.5//:parking_lot\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"parking_lot\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-intrusive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "crates_io__futures-io-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-io/0.3.32/download" + ], + "strip_prefix": "futures-io-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_io\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-io\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-macro-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-macro/0.3.32/download" + ], + "strip_prefix": "futures-macro-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"futures_macro\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-sink-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-sink/0.3.32/download" + ], + "strip_prefix": "futures-sink-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_sink\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-sink\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-task-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-task/0.3.32/download" + ], + "strip_prefix": "futures-task-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_task\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-task\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__futures-timer-3.0.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-timer/3.0.3/download" + ], + "strip_prefix": "futures-timer-3.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_timer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-timer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.0.3\",\n)\n" + } + }, + "crates_io__futures-util-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/futures-util/0.3.32/download" + ], + "strip_prefix": "futures-util-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"futures_util\",\n deps = [\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-io-0.3.32//:futures_io\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__futures-task-0.3.32//:futures_task\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__slab-0.4.12//:slab\",\n ],\n proc_macro_deps = [\n \"@crates_io__futures-macro-0.3.32//:futures_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"async-await\",\n \"async-await-macro\",\n \"channel\",\n \"futures-channel\",\n \"futures-io\",\n \"futures-macro\",\n \"futures-sink\",\n \"io\",\n \"memchr\",\n \"sink\",\n \"slab\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"default\", # wasm32-unknown-unknown\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"default\", # wasm32-wasip1\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=futures-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__fxhash-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/fxhash/0.2.1/download" + ], + "strip_prefix": "fxhash-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"fxhash\",\n deps = [\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=fxhash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__generic-array-0.14.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/generic-array/0.14.7/download" + ], + "strip_prefix": "generic-array-0.14.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"generic_array\",\n deps = [\n \"@crates_io__generic-array-0.14.7//:build_script_build\",\n \"@crates_io__typenum-1.19.0//:typenum\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"more_lengths\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=generic-array\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.7\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"more_lengths\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__version_check-0.9.5//:version_check\",\n ],\n edition = \"2015\",\n pkg_name = \"generic-array\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=generic-array\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.14.7\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__getrandom-0.2.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/getrandom/0.2.17/download" + ], + "strip_prefix": "getrandom-0.2.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"getrandom\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # wasm32-unknown-unknown\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # wasm32-unknown-unknown\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__wasi-0.11.1-wasi-snapshot-preview1//:wasi\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"js\", # wasm32-unknown-unknown\n \"js-sys\", # wasm32-unknown-unknown\n \"wasm-bindgen\", # wasm32-unknown-unknown\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.17\",\n)\n" + } + }, + "crates_io__getrandom-0.3.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/getrandom/0.3.4/download" + ], + "strip_prefix": "getrandom-0.3.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"getrandom\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__getrandom-0.3.4//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"getrandom\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__getrandom-0.4.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/getrandom/0.4.2/download" + ], + "strip_prefix": "getrandom-0.4.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"getrandom\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__getrandom-0.4.2//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2024\",\n pkg_name = \"getrandom\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=getrandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__h2-0.3.27": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/h2/0.3.27/download" + ], + "strip_prefix": "h2-0.3.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"h2\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__fnv-1.0.7//:fnv\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__slab-0.4.12//:slab\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=h2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.27\",\n)\n" + } + }, + "crates_io__h2-0.4.13": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/h2/0.4.13/download" + ], + "strip_prefix": "h2-0.4.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"h2\",\n deps = [\n \"@crates_io__atomic-waker-1.1.2//:atomic_waker\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__fnv-1.0.7//:fnv\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__slab-0.4.12//:slab\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=h2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.13\",\n)\n" + } + }, + "crates_io__half-2.7.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/half/2.7.1/download" + ], + "strip_prefix": "half-2.7.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"half\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__zerocopy-0.8.42//:zerocopy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=half\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.7.1\",\n)\n" + } + }, + "crates_io__hashbrown-0.12.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.12.3/download" + ], + "strip_prefix": "hashbrown-0.12.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"raw\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.3\",\n)\n" + } + }, + "crates_io__hashbrown-0.14.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.14.5/download" + ], + "strip_prefix": "hashbrown-0.14.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"raw\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.5\",\n)\n" + } + }, + "crates_io__hashbrown-0.15.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.15.5/download" + ], + "strip_prefix": "hashbrown-0.15.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n deps = [\n \"@crates_io__allocator-api2-0.2.21//:allocator_api2\",\n \"@crates_io__equivalent-1.0.2//:equivalent\",\n \"@crates_io__foldhash-0.1.5//:foldhash\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"allocator-api2\",\n \"default\",\n \"default-hasher\",\n \"equivalent\",\n \"inline-more\",\n \"raw-entry\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.15.5\",\n)\n" + } + }, + "crates_io__hashbrown-0.16.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashbrown/0.16.1/download" + ], + "strip_prefix": "hashbrown-0.16.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashbrown\",\n deps = [\n \"@crates_io__allocator-api2-0.2.21//:allocator_api2\",\n \"@crates_io__equivalent-1.0.2//:equivalent\",\n \"@crates_io__foldhash-0.2.0//:foldhash\",\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"allocator-api2\",\n \"default\",\n \"default-hasher\",\n \"equivalent\",\n \"inline-more\",\n \"raw-entry\",\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashbrown\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.16.1\",\n)\n" + } + }, + "crates_io__hashlink-0.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hashlink/0.10.0/download" + ], + "strip_prefix": "hashlink-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hashlink\",\n deps = [\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hashlink\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "crates_io__hdrhistogram-7.5.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hdrhistogram/7.5.4/download" + ], + "strip_prefix": "hdrhistogram-7.5.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hdrhistogram\",\n deps = [\n \"@crates_io__base64-0.21.7//:base64\",\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n \"@crates_io__flate2-1.1.9//:flate2\",\n \"@crates_io__nom-7.1.3//:nom\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"base64\",\n \"flate2\",\n \"nom\",\n \"serialization\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hdrhistogram\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"7.5.4\",\n)\n" + } + }, + "crates_io__headers-0.3.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/headers/0.3.9/download" + ], + "strip_prefix": "headers-0.3.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"headers\",\n deps = [\n \"@crates_io__base64-0.21.7//:base64\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__headers-core-0.2.0//:headers_core\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__httpdate-1.0.3//:httpdate\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__sha1-0.10.6//:sha1\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=headers\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.9\",\n)\n" + } + }, + "crates_io__headers-core-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/headers-core/0.2.0/download" + ], + "strip_prefix": "headers-core-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"headers_core\",\n deps = [\n \"@crates_io__http-0.2.12//:http\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=headers-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "crates_io__heck-0.4.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/heck/0.4.1/download" + ], + "strip_prefix": "heck-0.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"heck\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=heck\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.1\",\n)\n" + } + }, + "crates_io__heck-0.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/heck/0.5.0/download" + ], + "strip_prefix": "heck-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"heck\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=heck\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "crates_io__hermit-abi-0.5.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hermit-abi/0.5.2/download" + ], + "strip_prefix": "hermit-abi-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hermit_abi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hermit-abi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "crates_io__hex-0.4.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hex/0.4.3/download" + ], + "strip_prefix": "hex-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n" + } + }, + "crates_io__hkdf-0.12.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hkdf/0.12.4/download" + ], + "strip_prefix": "hkdf-0.12.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hkdf\",\n deps = [\n \"@crates_io__hmac-0.12.1//:hmac\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hkdf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.4\",\n)\n" + } + }, + "crates_io__hmac-0.12.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hmac/0.12.1/download" + ], + "strip_prefix": "hmac-0.12.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hmac\",\n deps = [\n \"@crates_io__digest-0.10.7//:digest\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hmac\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.1\",\n)\n" + } + }, + "crates_io__home-0.5.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/home/0.5.12/download" + ], + "strip_prefix": "home-0.5.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"home\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=home\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.12\",\n)\n" + } + }, + "crates_io__html5ever-0.27.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/html5ever/0.27.0/download" + ], + "strip_prefix": "html5ever-0.27.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"html5ever\",\n deps = [\n \"@crates_io__html5ever-0.27.0//:build_script_build\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__mac-0.1.1//:mac\",\n \"@crates_io__markup5ever-0.12.1//:markup5ever\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=html5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.27.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n edition = \"2021\",\n pkg_name = \"html5ever\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=html5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.27.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__htr-0.5.27": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8b0e5190458ce24b51fd95e34be40d23b8ec15e1d6cef76a8f8f321744efefc8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/htr/0.5.27/download" + ], + "strip_prefix": "htr-0.5.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"htr\",\n deps = [\n \"@crates_io__convert_case-0.5.0//:convert_case\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=htr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.27\",\n)\n" + } + }, + "crates_io__http-0.2.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http/0.2.12/download" + ], + "strip_prefix": "http-0.2.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__fnv-1.0.7//:fnv\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.12\",\n)\n" + } + }, + "crates_io__http-1.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http/1.4.0/download" + ], + "strip_prefix": "http-1.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.4.0\",\n)\n" + } + }, + "crates_io__http-body-0.4.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http-body/0.4.6/download" + ], + "strip_prefix": "http-body-0.4.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http_body\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http-body\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.6\",\n)\n" + } + }, + "crates_io__http-body-1.0.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http-body/1.0.1/download" + ], + "strip_prefix": "http-body-1.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http_body\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__http-1.4.0//:http\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http-body\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1\",\n)\n" + } + }, + "crates_io__http-body-util-0.1.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/http-body-util/0.1.3/download" + ], + "strip_prefix": "http-body-util-0.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"http_body_util\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=http-body-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.3\",\n)\n" + } + }, + "crates_io__httparse-1.10.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/httparse/1.10.1/download" + ], + "strip_prefix": "httparse-1.10.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"httparse\",\n deps = [\n \"@crates_io__httparse-1.10.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httparse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.10.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"httparse\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httparse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.10.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__httpdate-1.0.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/httpdate/1.0.3/download" + ], + "strip_prefix": "httpdate-1.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"httpdate\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=httpdate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.3\",\n)\n" + } + }, + "crates_io__humantime-2.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/humantime/2.3.0/download" + ], + "strip_prefix": "humantime-2.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"humantime\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=humantime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.0\",\n)\n" + } + }, + "crates_io__hyper-0.14.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper/0.14.32/download" + ], + "strip_prefix": "hyper-0.14.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__h2-0.3.27//:h2\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__http-body-0.4.6//:http_body\",\n \"@crates_io__httparse-1.10.1//:httparse\",\n \"@crates_io__httpdate-1.0.3//:httpdate\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__socket2-0.5.10//:socket2\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__want-0.3.1//:want\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"default\",\n \"h2\",\n \"http1\",\n \"http2\",\n \"server\",\n \"socket2\",\n \"stream\",\n \"tcp\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.32\",\n)\n" + } + }, + "crates_io__hyper-1.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper/1.8.1/download" + ], + "strip_prefix": "hyper-1.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper\",\n deps = [\n \"@crates_io__atomic-waker-1.1.2//:atomic_waker\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__h2-0.4.13//:h2\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__httparse-1.10.1//:httparse\",\n \"@crates_io__httpdate-1.0.3//:httpdate\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__pin-utils-0.1.0//:pin_utils\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__want-0.3.1//:want\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"default\",\n \"http1\",\n \"http2\",\n \"server\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.1\",\n)\n" + } + }, + "crates_io__hyper-rustls-0.27.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-rustls/0.27.7/download" + ], + "strip_prefix": "hyper-rustls-0.27.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_rustls\",\n deps = [\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__hyper-1.8.1//:hyper\",\n \"@crates_io__hyper-util-0.1.20//:hyper_util\",\n \"@crates_io__rustls-0.23.37//:rustls\",\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__webpki-roots-1.0.6//:webpki_roots\",\n ],\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"http1\",\n \"http2\",\n \"ring\",\n \"tls12\",\n \"webpki-roots\",\n \"webpki-tokio\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.27.7\",\n)\n" + } + }, + "crates_io__hyper-timeout-0.5.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-timeout/0.5.2/download" + ], + "strip_prefix": "hyper-timeout-0.5.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_timeout\",\n deps = [\n \"@crates_io__hyper-1.8.1//:hyper\",\n \"@crates_io__hyper-util-0.1.20//:hyper_util\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-timeout\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.2\",\n)\n" + } + }, + "crates_io__hyper-util-0.1.20": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/hyper-util/0.1.20/download" + ], + "strip_prefix": "hyper-util-0.1.20", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"hyper_util\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__hyper-1.8.1//:hyper\",\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__socket2-0.6.3//:socket2\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__base64-0.22.1//:base64\", # aarch64-apple-darwin\n \"@crates_io__ipnet-2.12.0//:ipnet\", # aarch64-apple-darwin\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # aarch64-apple-darwin\n \"@crates_io__system-configuration-0.7.0//:system_configuration\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__base64-0.22.1//:base64\", # aarch64-unknown-linux-gnu\n \"@crates_io__ipnet-2.12.0//:ipnet\", # aarch64-unknown-linux-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__base64-0.22.1//:base64\", # x86_64-pc-windows-msvc\n \"@crates_io__ipnet-2.12.0//:ipnet\", # x86_64-pc-windows-msvc\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-pc-windows-msvc\n \"@crates_io__windows-registry-0.6.1//:windows_registry\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__base64-0.22.1//:base64\", # x86_64-unknown-linux-gnu\n \"@crates_io__ipnet-2.12.0//:ipnet\", # x86_64-unknown-linux-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__base64-0.22.1//:base64\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__ipnet-2.12.0//:ipnet\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"client\",\n \"client-legacy\",\n \"default\",\n \"http1\",\n \"http2\",\n \"server\",\n \"server-auto\",\n \"service\",\n \"tokio\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"client-proxy\", # aarch64-apple-darwin\n \"client-proxy-system\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"client-proxy\", # aarch64-unknown-linux-gnu\n \"client-proxy-system\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"client-proxy\", # x86_64-pc-windows-msvc\n \"client-proxy-system\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"client-proxy\", # x86_64-unknown-linux-gnu\n \"client-proxy-system\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"client-proxy\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"client-proxy-system\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=hyper-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.20\",\n)\n" + } + }, + "crates_io__icu_collections-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_collections/2.1.1/download" + ], + "strip_prefix": "icu_collections-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_collections\",\n deps = [\n \"@crates_io__potential_utf-0.1.4//:potential_utf\",\n \"@crates_io__yoke-0.8.1//:yoke\",\n \"@crates_io__zerofrom-0.1.6//:zerofrom\",\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@crates_io__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_collections\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__icu_locale_core-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_locale_core/2.1.1/download" + ], + "strip_prefix": "icu_locale_core-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_locale_core\",\n deps = [\n \"@crates_io__litemap-0.8.1//:litemap\",\n \"@crates_io__tinystr-0.8.2//:tinystr\",\n \"@crates_io__writeable-0.6.2//:writeable\",\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@crates_io__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_locale_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__icu_normalizer-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_normalizer/2.1.1/download" + ], + "strip_prefix": "icu_normalizer-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_normalizer\",\n deps = [\n \"@crates_io__icu_collections-2.1.1//:icu_collections\",\n \"@crates_io__icu_normalizer_data-2.1.1//:icu_normalizer_data\",\n \"@crates_io__icu_provider-2.1.1//:icu_provider\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__icu_normalizer_data-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_normalizer_data/2.1.1/download" + ], + "strip_prefix": "icu_normalizer_data-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_normalizer_data\",\n deps = [\n \"@crates_io__icu_normalizer_data-2.1.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"icu_normalizer_data\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_normalizer_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__icu_properties-2.1.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_properties/2.1.2/download" + ], + "strip_prefix": "icu_properties-2.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_properties\",\n deps = [\n \"@crates_io__icu_collections-2.1.1//:icu_collections\",\n \"@crates_io__icu_locale_core-2.1.1//:icu_locale_core\",\n \"@crates_io__icu_properties_data-2.1.2//:icu_properties_data\",\n \"@crates_io__icu_provider-2.1.1//:icu_provider\",\n \"@crates_io__zerotrie-0.2.3//:zerotrie\",\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.2\",\n)\n" + } + }, + "crates_io__icu_properties_data-2.1.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_properties_data/2.1.2/download" + ], + "strip_prefix": "icu_properties_data-2.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_properties_data\",\n deps = [\n \"@crates_io__icu_properties_data-2.1.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"icu_properties_data\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_properties_data\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__icu_provider-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/icu_provider/2.1.1/download" + ], + "strip_prefix": "icu_provider-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"icu_provider\",\n deps = [\n \"@crates_io__icu_locale_core-2.1.1//:icu_locale_core\",\n \"@crates_io__writeable-0.6.2//:writeable\",\n \"@crates_io__yoke-0.8.1//:yoke\",\n \"@crates_io__zerofrom-0.1.6//:zerofrom\",\n \"@crates_io__zerotrie-0.2.3//:zerotrie\",\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@crates_io__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"baked\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=icu_provider\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__id-arena-2.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/id-arena/2.3.0/download" + ], + "strip_prefix": "id-arena-2.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"id_arena\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=id-arena\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.0\",\n)\n" + } + }, + "crates_io__idna-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/idna/1.1.0/download" + ], + "strip_prefix": "idna-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"idna\",\n deps = [\n \"@crates_io__idna_adapter-1.2.1//:idna_adapter\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__utf8_iter-1.0.4//:utf8_iter\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"compiled_data\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=idna\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__idna_adapter-1.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/idna_adapter/1.2.1/download" + ], + "strip_prefix": "idna_adapter-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"idna_adapter\",\n deps = [\n \"@crates_io__icu_normalizer-2.1.1//:icu_normalizer\",\n \"@crates_io__icu_properties-2.1.2//:icu_properties\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"compiled_data\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=idna_adapter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "crates_io__indexmap-1.9.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/indexmap/1.9.3/download" + ], + "strip_prefix": "indexmap-1.9.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"indexmap\",\n deps = [\n \"@crates_io__hashbrown-0.12.3//:hashbrown\",\n \"@crates_io__indexmap-1.9.3//:build_script_build\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=indexmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.9.3\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"serde\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__autocfg-1.5.0//:autocfg\",\n ],\n edition = \"2021\",\n pkg_name = \"indexmap\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=indexmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.9.3\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__indexmap-2.13.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/indexmap/2.13.0/download" + ], + "strip_prefix": "indexmap-2.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"indexmap\",\n deps = [\n \"@crates_io__equivalent-1.0.2//:equivalent\",\n \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__serde_core-1.0.228//:serde_core\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__serde_core-1.0.228//:serde_core\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__serde_core-1.0.228//:serde_core\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__serde_core-1.0.228//:serde_core\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__serde_core-1.0.228//:serde_core\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"serde\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"serde\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"serde\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"serde\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"serde\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=indexmap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.13.0\",\n)\n" + } + }, + "crates_io__io-uring-0.7.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/io-uring/0.7.11/download" + ], + "strip_prefix": "io-uring-0.7.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"io_uring\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__io-uring-0.7.11//:build_script_build\",\n \"@crates_io__libc-0.2.183//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=io-uring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.11\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"io-uring\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=io-uring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.7.11\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__ipnet-2.12.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ipnet/2.12.0/download" + ], + "strip_prefix": "ipnet-2.12.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ipnet\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ipnet\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.12.0\",\n)\n" + } + }, + "crates_io__iri-string-0.7.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/iri-string/0.7.10/download" + ], + "strip_prefix": "iri-string-0.7.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"iri_string\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=iri-string\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.10\",\n)\n" + } + }, + "crates_io__is_terminal_polyfill-1.70.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download" + ], + "strip_prefix": "is_terminal_polyfill-1.70.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"is_terminal_polyfill\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=is_terminal_polyfill\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.70.2\",\n)\n" + } + }, + "crates_io__itertools-0.13.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itertools/0.13.0/download" + ], + "strip_prefix": "itertools-0.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itertools\",\n deps = [\n \"@crates_io__either-1.15.0//:either\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"use_alloc\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itertools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.0\",\n)\n" + } + }, + "crates_io__itertools-0.14.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itertools/0.14.0/download" + ], + "strip_prefix": "itertools-0.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itertools\",\n deps = [\n \"@crates_io__either-1.15.0//:either\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"use_alloc\",\n \"use_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itertools\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.0\",\n)\n" + } + }, + "crates_io__itoa-1.0.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/itoa/1.0.17/download" + ], + "strip_prefix": "itoa-1.0.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"itoa\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=itoa\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.17\",\n)\n" + } + }, + "crates_io__jiff-0.2.23": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jiff/0.2.23/download" + ], + "strip_prefix": "jiff-0.2.23", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jiff\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jiff\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.23\",\n)\n" + } + }, + "crates_io__jiff-static-0.2.23": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jiff-static/0.2.23/download" + ], + "strip_prefix": "jiff-static-0.2.23", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"jiff_static\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jiff-static\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.23\",\n)\n" + } + }, + "crates_io__jni-0.21.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jni/0.21.1/download" + ], + "strip_prefix": "jni-0.21.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jni\",\n deps = [\n \"@crates_io__cesu8-1.1.0//:cesu8\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__combine-4.6.7//:combine\",\n \"@crates_io__jni-sys-0.3.0//:jni_sys\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__thiserror-1.0.69//:thiserror\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.45.0//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jni\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.21.1\",\n)\n" + } + }, + "crates_io__jni-sys-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jni-sys/0.3.0/download" + ], + "strip_prefix": "jni-sys-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jni_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jni-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "crates_io__jobserver-0.1.34": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/jobserver/0.1.34/download" + ], + "strip_prefix": "jobserver-0.1.34", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"jobserver\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__getrandom-0.3.4//:getrandom\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=jobserver\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.34\",\n)\n" + } + }, + "crates_io__js-sys-0.3.91": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/js-sys/0.3.91/download" + ], + "strip_prefix": "js-sys-0.3.91", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"js_sys\",\n deps = [\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n \"unsafe-eval\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=js-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.91\",\n)\n" + } + }, + "crates_io__lazy_static-1.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lazy_static/1.5.0/download" + ], + "strip_prefix": "lazy_static-1.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lazy_static\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lazy_static\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.0\",\n)\n" + } + }, + "crates_io__leb128fmt-0.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/leb128fmt/0.1.0/download" + ], + "strip_prefix": "leb128fmt-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"leb128fmt\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=leb128fmt\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "crates_io__libc-0.2.183": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libc/0.2.183/download" + ], + "strip_prefix": "libc-0.2.183", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libc\",\n deps = [\n \"@crates_io__libc-0.2.183//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"extra_traits\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"extra_traits\", # wasm32-wasip1\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.183\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"extra_traits\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"extra_traits\", # wasm32-wasip1\n ],\n \"//conditions:default\": [],\n }),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"libc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.183\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__libm-0.2.16": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libm/0.2.16/download" + ], + "strip_prefix": "libm-0.2.16", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libm\",\n deps = [\n \"@crates_io__libm-0.2.16//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"arch\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.16\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"arch\",\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"libm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.16\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__libredox-0.1.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libredox/0.1.14/download" + ], + "strip_prefix": "libredox-0.1.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libredox\",\n deps = [\n \"@crates_io__redox_syscall-0.7.3//:syscall\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libredox\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.14\",\n)\n" + } + }, + "crates_io__libsqlite3-sys-0.30.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/libsqlite3-sys/0.30.1/download" + ], + "strip_prefix": "libsqlite3-sys-0.30.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"libsqlite3_sys\",\n deps = [\n \"@crates_io__libsqlite3-sys-0.30.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bundled\",\n \"bundled_bindings\",\n \"cc\",\n \"pkg-config\",\n \"unlock_notify\",\n \"vcpkg\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libsqlite3-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.30.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bundled\",\n \"bundled_bindings\",\n \"cc\",\n \"pkg-config\",\n \"unlock_notify\",\n \"vcpkg\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n \"@crates_io__pkg-config-0.3.32//:pkg_config\",\n \"@crates_io__vcpkg-0.2.15//:vcpkg\",\n ],\n edition = \"2021\",\n links = \"sqlite3\",\n pkg_name = \"libsqlite3-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=libsqlite3-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.30.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__linux-raw-sys-0.4.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/linux-raw-sys/0.4.15/download" + ], + "strip_prefix": "linux-raw-sys-0.4.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"linux_raw_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"elf\",\n \"errno\",\n \"general\",\n \"ioctl\",\n \"no_std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linux-raw-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.15\",\n)\n" + } + }, + "crates_io__linux-raw-sys-0.12.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/linux-raw-sys/0.12.1/download" + ], + "strip_prefix": "linux-raw-sys-0.12.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"linux_raw_sys\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linux-raw-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.1\",\n)\n" + } + }, + "crates_io__litemap-0.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/litemap/0.8.1/download" + ], + "strip_prefix": "litemap-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"litemap\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=litemap\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "crates_io__litrs-1.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/litrs/1.0.0/download" + ], + "strip_prefix": "litrs-1.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"litrs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=litrs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.0\",\n)\n" + } + }, + "crates_io__llm_models_spider-0.1.42": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2dff3eaea579ea35091f7899d095d96a03ccae3e72ee5ad7b69b7b38b0fd7694", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/llm_models_spider/0.1.42/download" + ], + "strip_prefix": "llm_models_spider-0.1.42", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"llm_models_spider\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=llm_models_spider\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.42\",\n)\n" + } + }, + "crates_io__llm_readability-0.0.13": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2dd03f1473eaaf56fe7dccef40ad64626b98f4dba23af0ab7fcfc7d2d6b6845c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/llm_readability/0.0.13/download" + ], + "strip_prefix": "llm_readability-0.0.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"llm_readability\",\n deps = [\n \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n \"@crates_io__html5ever-0.27.0//:html5ever\",\n \"@crates_io__markup5ever-0.13.0//:markup5ever\",\n \"@crates_io__markup5ever_rcdom-0.3.0//:markup5ever_rcdom\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=llm_readability\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.13\",\n)\n" + } + }, + "crates_io__lock_api-0.4.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lock_api/0.4.14/download" + ], + "strip_prefix": "lock_api-0.4.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lock_api\",\n deps = [\n \"@crates_io__scopeguard-1.2.0//:scopeguard\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"atomic_usize\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lock_api\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.14\",\n)\n" + } + }, + "crates_io__log-0.4.29": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/log/0.4.29/download" + ], + "strip_prefix": "log-0.4.29", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"log\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=log\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.29\",\n)\n" + } + }, + "crates_io__lol_html-2.7.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5ff94cb6aef6ee52afd2c69331e9109906d855e82bd241f3110dfdf6185899ab", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lol_html/2.7.2/download" + ], + "strip_prefix": "lol_html-2.7.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lol_html\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__cssparser-0.36.0//:cssparser\",\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\",\n \"@crates_io__foldhash-0.2.0//:foldhash\",\n \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__precomputed-hash-0.1.1//:precomputed_hash\",\n \"@crates_io__selectors-0.33.0//:selectors\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lol_html\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.7.2\",\n)\n" + } + }, + "crates_io__lru-slab-0.1.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/lru-slab/0.1.2/download" + ], + "strip_prefix": "lru-slab-0.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"lru_slab\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=lru-slab\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n" + } + }, + "crates_io__mac-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/mac/0.1.1/download" + ], + "strip_prefix": "mac-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"mac\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mac\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__markup5ever-0.12.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/markup5ever/0.12.1/download" + ], + "strip_prefix": "markup5ever-0.12.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"markup5ever\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__markup5ever-0.12.1//:build_script_build\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__string_cache-0.8.9//:string_cache\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n \"@crates_io__string_cache_codegen-0.5.4//:string_cache_codegen\",\n ],\n edition = \"2021\",\n pkg_name = \"markup5ever\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.12.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__markup5ever-0.13.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d581ff8be69d08a2efa23a959d81aa22b739073f749f067348bd4f4ba4b69195", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/markup5ever/0.13.0/download" + ], + "strip_prefix": "markup5ever-0.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"markup5ever\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__markup5ever-0.13.0//:build_script_build\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__string_cache-0.8.9//:string_cache\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n \"@crates_io__string_cache_codegen-0.5.4//:string_cache_codegen\",\n ],\n edition = \"2021\",\n pkg_name = \"markup5ever\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=markup5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.13.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__markup5ever_rcdom-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/markup5ever_rcdom/0.3.0/download" + ], + "strip_prefix": "markup5ever_rcdom-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"markup5ever_rcdom\",\n deps = [\n \"@crates_io__html5ever-0.27.0//:html5ever\",\n \"@crates_io__markup5ever-0.12.1//:markup5ever\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n \"@crates_io__xml5ever-0.18.1//:xml5ever\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=markup5ever_rcdom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "crates_io__matchers-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/matchers/0.2.0/download" + ], + "strip_prefix": "matchers-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"matchers\",\n deps = [\n \"@crates_io__regex-automata-0.4.14//:regex_automata\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=matchers\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "crates_io__matchit-0.7.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/matchit/0.7.3/download" + ], + "strip_prefix": "matchit-0.7.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"matchit\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=matchit\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.3\",\n)\n" + } + }, + "crates_io__matrixmultiply-0.3.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/matrixmultiply/0.3.10/download" + ], + "strip_prefix": "matrixmultiply-0.3.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"matrixmultiply\",\n deps = [\n \"@crates_io__matrixmultiply-0.3.10//:build_script_build\",\n \"@crates_io__rawpointer-0.2.1//:rawpointer\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=matrixmultiply\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.10\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__autocfg-1.5.0//:autocfg\",\n ],\n edition = \"2018\",\n pkg_name = \"matrixmultiply\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=matrixmultiply\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.10\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__md-5-0.10.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/md-5/0.10.6/download" + ], + "strip_prefix": "md-5-0.10.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"md5\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__digest-0.10.7//:digest\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=md-5\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.6\",\n)\n" + } + }, + "crates_io__memchr-2.8.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/memchr/2.8.0/download" + ], + "strip_prefix": "memchr-2.8.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.8.0\",\n)\n" + } + }, + "crates_io__mime-0.3.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/mime/0.3.17/download" + ], + "strip_prefix": "mime-0.3.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"mime\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.17\",\n)\n" + } + }, + "crates_io__mime_guess-2.0.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/mime_guess/2.0.5/download" + ], + "strip_prefix": "mime_guess-2.0.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"mime_guess\",\n deps = [\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__mime_guess-2.0.5//:build_script_build\",\n \"@crates_io__unicase-2.9.0//:unicase\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"rev-mappings\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mime_guess\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"rev-mappings\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__unicase-2.9.0//:unicase\",\n ],\n edition = \"2015\",\n pkg_name = \"mime_guess\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mime_guess\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.0.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__minimal-lexical-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/minimal-lexical/0.2.1/download" + ], + "strip_prefix": "minimal-lexical-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"minimal_lexical\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=minimal-lexical\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__miniz_oxide-0.8.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/miniz_oxide/0.8.9/download" + ], + "strip_prefix": "miniz_oxide-0.8.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"miniz_oxide\",\n deps = [\n \"@crates_io__adler2-2.0.1//:adler2\",\n \"@crates_io__simd-adler32-0.3.8//:simd_adler32\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"simd\",\n \"simd-adler32\",\n \"with-alloc\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=miniz_oxide\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.9\",\n)\n" + } + }, + "crates_io__mio-1.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/mio/1.1.1/download" + ], + "strip_prefix": "mio-1.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"mio\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_os = \"wasi\")\n \"@crates_io__wasi-0.11.1-wasi-snapshot-preview1//:wasi\", # cfg(target_os = \"wasi\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"net\",\n \"os-ext\",\n \"os-poll\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=mio\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.1\",\n)\n" + } + }, + "crates_io__multer-2.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/multer/2.1.0/download" + ], + "strip_prefix": "multer-2.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"multer\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__httparse-1.10.1//:httparse\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__multer-2.1.0//:build_script_build\",\n \"@crates_io__spin-0.9.8//:spin\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=multer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__version_check-0.9.5//:version_check\",\n ],\n edition = \"2018\",\n pkg_name = \"multer\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=multer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.1.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__nalgebra-0.33.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nalgebra/0.33.2/download" + ], + "strip_prefix": "nalgebra-0.33.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nalgebra\",\n deps = [\n \"@crates_io__approx-0.5.1//:approx\",\n \"@crates_io__matrixmultiply-0.3.10//:matrixmultiply\",\n \"@crates_io__num-complex-0.4.6//:num_complex\",\n \"@crates_io__num-rational-0.4.2//:num_rational\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__rand-0.8.5//:rand\",\n \"@crates_io__rand_distr-0.4.3//:rand_distr\",\n \"@crates_io__simba-0.9.1//:simba\",\n \"@crates_io__typenum-1.19.0//:typenum\",\n ],\n aliases = {\n \"@crates_io__rand-0.8.5//:rand\": \"rand_package\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"matrixmultiply\",\n \"rand\",\n \"rand-no-std\",\n \"rand-package\",\n \"rand_distr\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nalgebra\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.33.2\",\n)\n" + } + }, + "crates_io__native-tls-0.2.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/native-tls/0.2.18/download" + ], + "strip_prefix": "native-tls-0.2.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"native_tls\",\n deps = [\n \"@crates_io__native-tls-0.2.18//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_vendor = \"apple\")\n \"@crates_io__security-framework-3.7.0//:security_framework\", # cfg(target_vendor = \"apple\")\n \"@crates_io__security-framework-sys-2.17.0//:security_framework_sys\", # cfg(target_vendor = \"apple\")\n \"@crates_io__tempfile-3.27.0//:tempfile\", # cfg(target_os = \"macos\")\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__log-0.4.29//:log\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-0.10.76//:openssl\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__log-0.4.29//:log\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-0.10.76//:openssl\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__log-0.4.29//:log\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-0.10.76//:openssl\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__schannel-0.1.29//:schannel\", # cfg(target_os = \"windows\")\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__log-0.4.29//:log\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-0.10.76//:openssl\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__log-0.4.29//:log\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-0.10.76//:openssl\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=native-tls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.18\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = select({\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\", # cfg(not(any(target_os = \"windows\", target_vendor = \"apple\")))\n ],\n \"//conditions:default\": [],\n }),\n edition = \"2021\",\n pkg_name = \"native-tls\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=native-tls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.18\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__new_debug_unreachable-1.0.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/new_debug_unreachable/1.0.6/download" + ], + "strip_prefix": "new_debug_unreachable-1.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"debug_unreachable\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=new_debug_unreachable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6\",\n)\n" + } + }, + "crates_io__nom-7.1.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/nom/7.1.3/download" + ], + "strip_prefix": "nom-7.1.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"nom\",\n deps = [\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__minimal-lexical-0.2.1//:minimal_lexical\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=nom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"7.1.3\",\n)\n" + } + }, + "crates_io__ntapi-0.4.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ntapi/0.4.3/download" + ], + "strip_prefix": "ntapi-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ntapi\",\n deps = [\n \"@crates_io__ntapi-0.4.3//:build_script_build\",\n \"@crates_io__winapi-0.3.9//:winapi\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"user\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ntapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"user\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"ntapi\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ntapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.3\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__num-bigint-0.4.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-bigint/0.4.6/download" + ], + "strip_prefix": "num-bigint-0.4.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_bigint\",\n deps = [\n \"@crates_io__num-integer-0.1.46//:num_integer\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-bigint\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.6\",\n)\n" + } + }, + "crates_io__num-bigint-dig-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-bigint-dig/0.8.6/download" + ], + "strip_prefix": "num-bigint-dig-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_bigint_dig\",\n deps = [\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__libm-0.2.16//:libm\",\n \"@crates_io__num-bigint-dig-0.8.6//:build_script_build\",\n \"@crates_io__num-integer-0.1.46//:num_integer\",\n \"@crates_io__num-iter-0.1.45//:num_iter\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-bigint-dig\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"num-bigint-dig\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-bigint-dig\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__num-complex-0.4.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-complex/0.4.6/download" + ], + "strip_prefix": "num-complex-0.4.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_complex\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-complex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.6\",\n)\n" + } + }, + "crates_io__num-conv-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-conv/0.2.0/download" + ], + "strip_prefix": "num-conv-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_conv\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-conv\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "crates_io__num-integer-0.1.46": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-integer/0.1.46/download" + ], + "strip_prefix": "num-integer-0.1.46", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_integer\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"i128\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-integer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.46\",\n)\n" + } + }, + "crates_io__num-iter-0.1.45": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-iter/0.1.45/download" + ], + "strip_prefix": "num-iter-0.1.45", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_iter\",\n deps = [\n \"@crates_io__num-integer-0.1.46//:num_integer\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-iter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.45\",\n)\n" + } + }, + "crates_io__num-rational-0.4.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-rational/0.4.2/download" + ], + "strip_prefix": "num-rational-0.4.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_rational\",\n deps = [\n \"@crates_io__num-integer-0.1.46//:num_integer\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-rational\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.2\",\n)\n" + } + }, + "crates_io__num-traits-0.2.19": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num-traits/0.2.19/download" + ], + "strip_prefix": "num-traits-0.2.19", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_traits\",\n deps = [\n \"@crates_io__libm-0.2.16//:libm\",\n \"@crates_io__num-traits-0.2.19//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"i128\",\n \"libm\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.19\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"i128\",\n \"libm\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__autocfg-1.5.0//:autocfg\",\n ],\n edition = \"2021\",\n pkg_name = \"num-traits\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.19\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__num_cpus-1.17.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num_cpus/1.17.0/download" + ], + "strip_prefix": "num_cpus-1.17.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_cpus\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(windows))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num_cpus\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.17.0\",\n)\n" + } + }, + "crates_io__num_enum-0.5.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num_enum/0.5.11/download" + ], + "strip_prefix": "num_enum-0.5.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_enum\",\n proc_macro_deps = [\n \"@crates_io__num_enum_derive-0.5.11//:num_enum_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num_enum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.11\",\n)\n" + } + }, + "crates_io__num_enum_derive-0.5.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/num_enum_derive/0.5.11/download" + ], + "strip_prefix": "num_enum_derive-0.5.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"num_enum_derive\",\n deps = [\n \"@crates_io__proc-macro-crate-1.3.1//:proc_macro_crate\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-1.0.109//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro-crate\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num_enum_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.11\",\n)\n" + } + }, + "crates_io__objc2-core-foundation-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/objc2-core-foundation/0.3.2/download" + ], + "strip_prefix": "objc2-core-foundation-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"objc2_core_foundation\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"CFArray\",\n \"CFBase\",\n \"CFData\",\n \"CFDictionary\",\n \"CFPlugInCOM\",\n \"CFRunLoop\",\n \"CFString\",\n \"CFURL\",\n \"CFUUID\",\n \"alloc\",\n \"bitflags\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=objc2-core-foundation\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__objc2-io-kit-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/objc2-io-kit/0.3.2/download" + ], + "strip_prefix": "objc2-io-kit-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"objc2_io_kit\",\n deps = [\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__objc2-core-foundation-0.3.2//:objc2_core_foundation\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"libc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=objc2-io-kit\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__once_cell-1.21.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/once_cell/1.21.4/download" + ], + "strip_prefix": "once_cell-1.21.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"once_cell\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"race\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=once_cell\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.21.4\",\n)\n" + } + }, + "crates_io__once_cell_polyfill-1.70.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/once_cell_polyfill/1.70.2/download" + ], + "strip_prefix": "once_cell_polyfill-1.70.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"once_cell_polyfill\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=once_cell_polyfill\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.70.2\",\n)\n" + } + }, + "crates_io__oorandom-11.1.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/oorandom/11.1.5/download" + ], + "strip_prefix": "oorandom-11.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"oorandom\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=oorandom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"11.1.5\",\n)\n" + } + }, + "crates_io__openssl-0.10.76": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/openssl/0.10.76/download" + ], + "strip_prefix": "openssl-0.10.76", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"openssl\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__foreign-types-0.3.2//:foreign_types\",\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__openssl-0.10.76//:build_script_build\",\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\",\n ],\n proc_macro_deps = [\n \"@crates_io__openssl-macros-0.1.1//:openssl_macros\",\n ],\n aliases = {\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\": \"ffi\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.76\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__openssl-sys-0.9.112//:openssl_sys\",\n ],\n edition = \"2021\",\n pkg_name = \"openssl\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.10.76\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__openssl-macros-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/openssl-macros/0.1.1/download" + ], + "strip_prefix": "openssl-macros-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"openssl_macros\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__openssl-probe-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/openssl-probe/0.2.1/download" + ], + "strip_prefix": "openssl-probe-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"openssl_probe\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl-probe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__openssl-sys-0.9.112": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/openssl-sys/0.9.112/download" + ], + "strip_prefix": "openssl-sys-0.9.112", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"openssl_sys\",\n deps = [\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__openssl-sys-0.9.112//:build_script_main\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.112\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_main\",\n crate_root = \"build/main.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n \"@crates_io__pkg-config-0.3.32//:pkg_config\",\n \"@crates_io__vcpkg-0.2.15//:vcpkg\",\n ],\n edition = \"2021\",\n links = \"openssl\",\n pkg_name = \"openssl-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=openssl-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.9.112\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_main\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__page_size-0.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/page_size/0.6.0/download" + ], + "strip_prefix": "page_size-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"page_size\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__winapi-0.3.9//:winapi\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=page_size\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n" + } + }, + "crates_io__parking-2.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parking/2.2.1/download" + ], + "strip_prefix": "parking-2.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parking\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.2.1\",\n)\n" + } + }, + "crates_io__parking_lot-0.12.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parking_lot/0.12.5/download" + ], + "strip_prefix": "parking_lot-0.12.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parking_lot\",\n deps = [\n \"@crates_io__lock_api-0.4.14//:lock_api\",\n \"@crates_io__parking_lot_core-0.9.12//:parking_lot_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.5\",\n)\n" + } + }, + "crates_io__parking_lot_core-0.9.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/parking_lot_core/0.9.12/download" + ], + "strip_prefix": "parking_lot_core-0.9.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"parking_lot_core\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__parking_lot_core-0.9.12//:build_script_build\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-link-0.2.1//:windows_link\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.12\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"parking_lot_core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=parking_lot_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.9.12\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__paste-1.0.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/paste/1.0.15/download" + ], + "strip_prefix": "paste-1.0.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"paste\",\n deps = [\n \"@crates_io__paste-1.0.15//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=paste\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.15\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"paste\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=paste\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.15\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__pem-rfc7468-0.7.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pem-rfc7468/0.7.0/download" + ], + "strip_prefix": "pem-rfc7468-0.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pem_rfc7468\",\n deps = [\n \"@crates_io__base64ct-1.8.3//:base64ct\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pem-rfc7468\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.0\",\n)\n" + } + }, + "crates_io__percent-encoding-2.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/percent-encoding/2.3.2/download" + ], + "strip_prefix": "percent-encoding-2.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"percent_encoding\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=percent-encoding\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.2\",\n)\n" + } + }, + "crates_io__peresil-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/peresil/0.3.0/download" + ], + "strip_prefix": "peresil-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"peresil\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=peresil\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "crates_io__phf-0.10.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf/0.10.1/download" + ], + "strip_prefix": "phf-0.10.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf\",\n deps = [\n \"@crates_io__phf_shared-0.10.0//:phf_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.1\",\n)\n" + } + }, + "crates_io__phf-0.11.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf/0.11.3/download" + ], + "strip_prefix": "phf-0.11.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf\",\n deps = [\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n ],\n proc_macro_deps = [\n \"@crates_io__phf_macros-0.11.3//:phf_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"macros\",\n \"phf_macros\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.3\",\n)\n" + } + }, + "crates_io__phf-0.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf/0.13.1/download" + ], + "strip_prefix": "phf-0.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf\",\n deps = [\n \"@crates_io__phf_shared-0.13.1//:phf_shared\",\n ],\n proc_macro_deps = [\n \"@crates_io__phf_macros-0.13.1//:phf_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"macros\",\n \"phf_macros\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.1\",\n)\n" + } + }, + "crates_io__phf_codegen-0.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_codegen/0.10.0/download" + ], + "strip_prefix": "phf_codegen-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_codegen\",\n deps = [\n \"@crates_io__phf_generator-0.10.0//:phf_generator\",\n \"@crates_io__phf_shared-0.10.0//:phf_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_codegen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "crates_io__phf_codegen-0.11.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_codegen/0.11.3/download" + ], + "strip_prefix": "phf_codegen-0.11.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_codegen\",\n deps = [\n \"@crates_io__phf_generator-0.11.3//:phf_generator\",\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_codegen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.3\",\n)\n" + } + }, + "crates_io__phf_codegen-0.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_codegen/0.13.1/download" + ], + "strip_prefix": "phf_codegen-0.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_codegen\",\n deps = [\n \"@crates_io__phf_generator-0.13.1//:phf_generator\",\n \"@crates_io__phf_shared-0.13.1//:phf_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_codegen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.1\",\n)\n" + } + }, + "crates_io__phf_generator-0.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_generator/0.10.0/download" + ], + "strip_prefix": "phf_generator-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_generator\",\n deps = [\n \"@crates_io__phf_shared-0.10.0//:phf_shared\",\n \"@crates_io__rand-0.8.5//:rand\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_generator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "crates_io__phf_generator-0.11.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_generator/0.11.3/download" + ], + "strip_prefix": "phf_generator-0.11.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_generator\",\n deps = [\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n \"@crates_io__rand-0.8.5//:rand\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_generator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.3\",\n)\n" + } + }, + "crates_io__phf_generator-0.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_generator/0.13.1/download" + ], + "strip_prefix": "phf_generator-0.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_generator\",\n deps = [\n \"@crates_io__fastrand-2.3.0//:fastrand\",\n \"@crates_io__phf_shared-0.13.1//:phf_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_generator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.1\",\n)\n" + } + }, + "crates_io__phf_macros-0.11.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_macros/0.11.3/download" + ], + "strip_prefix": "phf_macros-0.11.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"phf_macros\",\n deps = [\n \"@crates_io__phf_generator-0.11.3//:phf_generator\",\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.3\",\n)\n" + } + }, + "crates_io__phf_macros-0.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_macros/0.13.1/download" + ], + "strip_prefix": "phf_macros-0.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"phf_macros\",\n deps = [\n \"@crates_io__phf_generator-0.13.1//:phf_generator\",\n \"@crates_io__phf_shared-0.13.1//:phf_shared\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.1\",\n)\n" + } + }, + "crates_io__phf_shared-0.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_shared/0.10.0/download" + ], + "strip_prefix": "phf_shared-0.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_shared\",\n deps = [\n \"@crates_io__siphasher-0.3.11//:siphasher\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.0\",\n)\n" + } + }, + "crates_io__phf_shared-0.11.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_shared/0.11.3/download" + ], + "strip_prefix": "phf_shared-0.11.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_shared\",\n deps = [\n \"@crates_io__siphasher-1.0.2//:siphasher\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.3\",\n)\n" + } + }, + "crates_io__phf_shared-0.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/phf_shared/0.13.1/download" + ], + "strip_prefix": "phf_shared-0.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"phf_shared\",\n deps = [\n \"@crates_io__siphasher-1.0.2//:siphasher\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=phf_shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.1\",\n)\n" + } + }, + "crates_io__pin-project-1.1.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project/1.1.11/download" + ], + "strip_prefix": "pin-project-1.1.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_project\",\n proc_macro_deps = [\n \"@crates_io__pin-project-internal-1.1.11//:pin_project_internal\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.11\",\n)\n" + } + }, + "crates_io__pin-project-internal-1.1.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project-internal/1.1.11/download" + ], + "strip_prefix": "pin-project-internal-1.1.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"pin_project_internal\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project-internal\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.11\",\n)\n" + } + }, + "crates_io__pin-project-lite-0.2.17": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-project-lite/0.2.17/download" + ], + "strip_prefix": "pin-project-lite-0.2.17", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_project_lite\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-project-lite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.17\",\n)\n" + } + }, + "crates_io__pin-utils-0.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pin-utils/0.1.0/download" + ], + "strip_prefix": "pin-utils-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pin_utils\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pin-utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "crates_io__pkcs1-0.7.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pkcs1/0.7.5/download" + ], + "strip_prefix": "pkcs1-0.7.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pkcs1\",\n deps = [\n \"@crates_io__der-0.7.10//:der\",\n \"@crates_io__spki-0.7.3//:spki\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pkcs1\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.5\",\n)\n" + } + }, + "crates_io__pkcs8-0.10.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pkcs8/0.10.2/download" + ], + "strip_prefix": "pkcs8-0.10.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pkcs8\",\n deps = [\n \"@crates_io__der-0.7.10//:der\",\n \"@crates_io__spki-0.7.3//:spki\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pkcs8\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.2\",\n)\n" + } + }, + "crates_io__pkg-config-0.3.32": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/pkg-config/0.3.32/download" + ], + "strip_prefix": "pkg-config-0.3.32", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"pkg_config\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=pkg-config\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.32\",\n)\n" + } + }, + "crates_io__plain-0.2.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plain/0.2.3/download" + ], + "strip_prefix": "plain-0.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plain\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plain\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.3\",\n)\n" + } + }, + "crates_io__plotters-0.3.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters/0.3.7/download" + ], + "strip_prefix": "plotters-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__plotters-backend-0.3.7//:plotters_backend\",\n \"@crates_io__plotters-svg-0.3.7//:plotters_svg\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))\n \"@crates_io__web-sys-0.3.91//:web_sys\", # cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"area_series\",\n \"line_series\",\n \"plotters-svg\",\n \"svg_backend\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "crates_io__plotters-backend-0.3.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters-backend/0.3.7/download" + ], + "strip_prefix": "plotters-backend-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters_backend\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters-backend\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "crates_io__plotters-svg-0.3.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/plotters-svg/0.3.7/download" + ], + "strip_prefix": "plotters-svg-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"plotters_svg\",\n deps = [\n \"@crates_io__plotters-backend-0.3.7//:plotters_backend\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=plotters-svg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "crates_io__portable-atomic-1.13.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/portable-atomic/1.13.1/download" + ], + "strip_prefix": "portable-atomic-1.13.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"portable_atomic\",\n deps = [\n \"@crates_io__portable-atomic-1.13.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.13.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"portable-atomic\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.13.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__portable-atomic-util-0.2.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/portable-atomic-util/0.2.5/download" + ], + "strip_prefix": "portable-atomic-util-0.2.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"portable_atomic_util\",\n deps = [\n \"@crates_io__portable-atomic-1.13.1//:portable_atomic\",\n \"@crates_io__portable-atomic-util-0.2.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"portable-atomic-util\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=portable-atomic-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__potential_utf-0.1.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/potential_utf/0.1.4/download" + ], + "strip_prefix": "potential_utf-0.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"potential_utf\",\n deps = [\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=potential_utf\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.4\",\n)\n" + } + }, + "crates_io__powerfmt-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/powerfmt/0.2.0/download" + ], + "strip_prefix": "powerfmt-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"powerfmt\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=powerfmt\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "crates_io__ppv-lite86-0.2.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ppv-lite86/0.2.21/download" + ], + "strip_prefix": "ppv-lite86-0.2.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ppv_lite86\",\n deps = [\n \"@crates_io__zerocopy-0.8.42//:zerocopy\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"simd\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ppv-lite86\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.21\",\n)\n" + } + }, + "crates_io__precomputed-hash-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/precomputed-hash/0.1.1/download" + ], + "strip_prefix": "precomputed-hash-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"precomputed_hash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=precomputed-hash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__prettyplease-0.2.37": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prettyplease/0.2.37/download" + ], + "strip_prefix": "prettyplease-0.2.37", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"prettyplease\",\n deps = [\n \"@crates_io__prettyplease-0.2.37//:build_script_build\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prettyplease\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.37\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"prettyplease02\",\n pkg_name = \"prettyplease\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prettyplease\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.37\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__proc-macro-crate-1.3.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro-crate/1.3.1/download" + ], + "strip_prefix": "proc-macro-crate-1.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro_crate\",\n deps = [\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__toml_edit-0.19.15//:toml_edit\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro-crate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.1\",\n)\n" + } + }, + "crates_io__proc-macro2-1.0.106": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/proc-macro2/1.0.106/download" + ], + "strip_prefix": "proc-macro2-1.0.106", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro2\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:build_script_build\",\n \"@crates_io__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.106\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"proc-macro2\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.106\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__prost-0.13.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prost/0.13.5/download" + ], + "strip_prefix": "prost-0.13.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"prost\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n ],\n proc_macro_deps = [\n \"@crates_io__prost-derive-0.13.5//:prost_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"derive\",\n \"prost-derive\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prost\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.5\",\n)\n" + } + }, + "crates_io__prost-derive-0.13.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prost-derive/0.13.5/download" + ], + "strip_prefix": "prost-derive-0.13.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"prost_derive\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__itertools-0.14.0//:itertools\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prost-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.5\",\n)\n" + } + }, + "crates_io__prost-types-0.13.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/prost-types/0.13.5/download" + ], + "strip_prefix": "prost-types-0.13.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"prost_types\",\n deps = [\n \"@crates_io__prost-0.13.5//:prost\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=prost-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.5\",\n)\n" + } + }, + "crates_io__psl-types-2.0.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/psl-types/2.0.11/download" + ], + "strip_prefix": "psl-types-2.0.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"psl_types\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=psl-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.11\",\n)\n" + } + }, + "crates_io__publicsuffix-2.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/publicsuffix/2.3.0/download" + ], + "strip_prefix": "publicsuffix-2.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"publicsuffix\",\n deps = [\n \"@crates_io__idna-1.1.0//:idna\",\n \"@crates_io__psl-types-2.0.11//:psl_types\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"idna\",\n \"punycode\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=publicsuffix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.3.0\",\n)\n" + } + }, + "crates_io__quick-error-1.2.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quick-error/1.2.3/download" + ], + "strip_prefix": "quick-error-1.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quick_error\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quick-error\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.3\",\n)\n" + } + }, + "crates_io__quick-xml-0.39.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quick-xml/0.39.2/download" + ], + "strip_prefix": "quick-xml-0.39.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quick_xml\",\n deps = [\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async-tokio\",\n \"default\",\n \"serde\",\n \"serialize\",\n \"tokio\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quick-xml\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.39.2\",\n)\n" + } + }, + "crates_io__quinn-0.11.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quinn/0.11.9/download" + ], + "strip_prefix": "quinn-0.11.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quinn\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__quinn-0.11.9//:build_script_build\",\n \"@crates_io__quinn-proto-0.11.14//:quinn_proto\",\n \"@crates_io__quinn-udp-0.5.14//:quinn_udp\",\n \"@crates_io__rustc-hash-2.1.1//:rustc_hash\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__web-time-1.1.0//:web_time\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"//conditions:default\": [],\n }),\n aliases = {\n \"@crates_io__quinn-proto-0.11.14//:quinn_proto\": \"proto\",\n \"@crates_io__quinn-udp-0.5.14//:quinn_udp\": \"udp\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quinn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.9\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cfg_aliases-0.2.1//:cfg_aliases\",\n ],\n edition = \"2021\",\n pkg_name = \"quinn\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quinn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.11.9\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__quinn-proto-0.11.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quinn-proto/0.11.14/download" + ], + "strip_prefix": "quinn-proto-0.11.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quinn_proto\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__lru-slab-0.1.2//:lru_slab\",\n \"@crates_io__rand-0.9.2//:rand\",\n \"@crates_io__rustc-hash-2.1.1//:rustc_hash\",\n \"@crates_io__slab-0.4.12//:slab\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tinyvec-1.10.0//:tinyvec\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__getrandom-0.3.4//:getrandom\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n \"@crates_io__ring-0.17.14//:ring\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n \"@crates_io__web-time-1.1.0//:web_time\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quinn-proto\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.14\",\n)\n" + } + }, + "crates_io__quinn-udp-0.5.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quinn-udp/0.5.14/download" + ], + "strip_prefix": "quinn-udp-0.5.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quinn_udp\",\n deps = [\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__quinn-udp-0.5.14//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__once_cell-1.21.4//:once_cell\", # cfg(windows)\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n \"@crates_io__windows-sys-0.60.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # cfg(not(all(target_family = \"wasm\", target_os = \"unknown\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quinn-udp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.14\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cfg_aliases-0.2.1//:cfg_aliases\",\n ],\n edition = \"2021\",\n pkg_name = \"quinn-udp\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quinn-udp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.5.14\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__quote-1.0.45": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/quote/1.0.45/download" + ], + "strip_prefix": "quote-1.0.45", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quote\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.45\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"quote\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.45\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__r-efi-5.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/r-efi/5.3.0/download" + ], + "strip_prefix": "r-efi-5.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"r_efi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=r-efi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"5.3.0\",\n)\n" + } + }, + "crates_io__r-efi-6.0.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/r-efi/6.0.0/download" + ], + "strip_prefix": "r-efi-6.0.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"r_efi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=r-efi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"6.0.0\",\n)\n" + } + }, + "crates_io__rand-0.8.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand/0.8.5/download" + ], + "strip_prefix": "rand-0.8.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand\",\n deps = [\n \"@crates_io__rand_chacha-0.3.1//:rand_chacha\",\n \"@crates_io__rand_core-0.6.4//:rand_core\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"getrandom\",\n \"libc\",\n \"rand_chacha\",\n \"small_rng\",\n \"std\",\n \"std_rng\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.5\",\n)\n" + } + }, + "crates_io__rand-0.9.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand/0.9.2/download" + ], + "strip_prefix": "rand-0.9.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand\",\n deps = [\n \"@crates_io__rand_chacha-0.9.0//:rand_chacha\",\n \"@crates_io__rand_core-0.9.5//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"os_rng\",\n \"small_rng\",\n \"std\",\n \"std_rng\",\n \"thread_rng\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.2\",\n)\n" + } + }, + "crates_io__rand_chacha-0.3.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_chacha/0.3.1/download" + ], + "strip_prefix": "rand_chacha-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_chacha\",\n deps = [\n \"@crates_io__ppv-lite86-0.2.21//:ppv_lite86\",\n \"@crates_io__rand_core-0.6.4//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_chacha\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "crates_io__rand_chacha-0.9.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_chacha/0.9.0/download" + ], + "strip_prefix": "rand_chacha-0.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_chacha\",\n deps = [\n \"@crates_io__ppv-lite86-0.2.21//:ppv_lite86\",\n \"@crates_io__rand_core-0.9.5//:rand_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_chacha\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.0\",\n)\n" + } + }, + "crates_io__rand_core-0.6.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_core/0.6.4/download" + ], + "strip_prefix": "rand_core-0.6.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_core\",\n deps = [\n \"@crates_io__getrandom-0.2.17//:getrandom\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"getrandom\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.4\",\n)\n" + } + }, + "crates_io__rand_core-0.9.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_core/0.9.5/download" + ], + "strip_prefix": "rand_core-0.9.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_core\",\n deps = [\n \"@crates_io__getrandom-0.3.4//:getrandom\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"os_rng\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.5\",\n)\n" + } + }, + "crates_io__rand_distr-0.4.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rand_distr/0.4.3/download" + ], + "strip_prefix": "rand_distr-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rand_distr\",\n deps = [\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__rand-0.8.5//:rand\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rand_distr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n" + } + }, + "crates_io__rawpointer-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rawpointer/0.2.1/download" + ], + "strip_prefix": "rawpointer-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rawpointer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rawpointer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__rayon-1.11.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rayon/1.11.0/download" + ], + "strip_prefix": "rayon-1.11.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rayon\",\n deps = [\n \"@crates_io__either-1.15.0//:either\",\n \"@crates_io__rayon-core-1.13.0//:rayon_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.11.0\",\n)\n" + } + }, + "crates_io__rayon-core-1.13.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rayon-core/1.13.0/download" + ], + "strip_prefix": "rayon-core-1.13.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rayon_core\",\n deps = [\n \"@crates_io__crossbeam-deque-0.8.6//:crossbeam_deque\",\n \"@crates_io__crossbeam-utils-0.8.21//:crossbeam_utils\",\n \"@crates_io__rayon-core-1.13.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.13.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"rayon-core\",\n pkg_name = \"rayon-core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rayon-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.13.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__redox_syscall-0.5.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/redox_syscall/0.5.18/download" + ], + "strip_prefix": "redox_syscall-0.5.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syscall\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=redox_syscall\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.18\",\n)\n" + } + }, + "crates_io__redox_syscall-0.7.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/redox_syscall/0.7.3/download" + ], + "strip_prefix": "redox_syscall-0.7.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syscall\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=redox_syscall\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.3\",\n)\n" + } + }, + "crates_io__regex-1.12.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex/1.12.3/download" + ], + "strip_prefix": "regex-1.12.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__regex-automata-0.4.14//:regex_automata\",\n \"@crates_io__regex-syntax-0.8.10//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"perf\",\n \"perf-backtrack\",\n \"perf-cache\",\n \"perf-dfa\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-onepass\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.12.3\",\n)\n" + } + }, + "crates_io__regex-automata-0.4.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-automata/0.4.14/download" + ], + "strip_prefix": "regex-automata-0.4.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_automata\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__regex-syntax-0.8.10//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"dfa-build\",\n \"dfa-onepass\",\n \"dfa-search\",\n \"hybrid\",\n \"meta\",\n \"nfa-backtrack\",\n \"nfa-pikevm\",\n \"nfa-thompson\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-literal-multisubstring\",\n \"perf-literal-substring\",\n \"std\",\n \"syntax\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n \"unicode-word-boundary\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-automata\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.14\",\n)\n" + } + }, + "crates_io__regex-syntax-0.8.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-syntax/0.8.10/download" + ], + "strip_prefix": "regex-syntax-0.8.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_syntax\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-syntax\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.10\",\n)\n" + } + }, + "crates_io__reqwest-0.12.28": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/reqwest/0.12.28/download" + ], + "strip_prefix": "reqwest-0.12.28", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"reqwest\",\n deps = [\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\",\n \"@crates_io__rustls-0.23.37//:rustls\",\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__serde_urlencoded-0.7.1//:serde_urlencoded\",\n \"@crates_io__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\",\n \"@crates_io__url-2.5.8//:url\",\n \"@crates_io__webpki-roots-1.0.6//:webpki_roots\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-futures-0.4.64//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__web-sys-0.3.91//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-futures-0.4.64//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__web-sys-0.3.91//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__rustls\",\n \"__rustls-ring\",\n \"__tls\",\n \"blocking\",\n \"json\",\n \"rustls-tls\",\n \"rustls-tls-webpki-roots\",\n \"rustls-tls-webpki-roots-no-provider\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=reqwest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.28\",\n)\n" + } + }, + "crates_io__reqwest-0.13.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/reqwest/0.13.2/download" + ], + "strip_prefix": "reqwest-0.13.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"reqwest\",\n deps = [\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__serde_urlencoded-0.7.1//:serde_urlencoded\",\n \"@crates_io__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@crates_io__url-2.5.8//:url\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__cookie-0.18.1//:cookie\", # aarch64-apple-darwin\n \"@crates_io__cookie_store-0.22.1//:cookie_store\", # aarch64-apple-darwin\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\", # aarch64-apple-darwin\n \"@crates_io__h2-0.4.13//:h2\", # aarch64-apple-darwin\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\", # aarch64-apple-darwin\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__mime-0.3.17//:mime\", # aarch64-apple-darwin\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__rustls-0.23.37//:rustls\", # aarch64-apple-darwin\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # aarch64-apple-darwin\n \"@crates_io__rustls-platform-verifier-0.6.2//:rustls_platform_verifier\", # aarch64-apple-darwin\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\", # aarch64-apple-darwin\n \"@crates_io__tokio-util-0.7.18//:tokio_util\", # aarch64-apple-darwin\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__cookie-0.18.1//:cookie\", # aarch64-unknown-linux-gnu\n \"@crates_io__cookie_store-0.22.1//:cookie_store\", # aarch64-unknown-linux-gnu\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\", # aarch64-unknown-linux-gnu\n \"@crates_io__h2-0.4.13//:h2\", # aarch64-unknown-linux-gnu\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\", # aarch64-unknown-linux-gnu\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__mime-0.3.17//:mime\", # aarch64-unknown-linux-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__rustls-0.23.37//:rustls\", # aarch64-unknown-linux-gnu\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # aarch64-unknown-linux-gnu\n \"@crates_io__rustls-platform-verifier-0.6.2//:rustls_platform_verifier\", # aarch64-unknown-linux-gnu\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\", # aarch64-unknown-linux-gnu\n \"@crates_io__tokio-util-0.7.18//:tokio_util\", # aarch64-unknown-linux-gnu\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-futures-0.4.64//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-streams-0.5.0//:wasm_streams\", # wasm32-unknown-unknown\n \"@crates_io__web-sys-0.3.91//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-bindgen-futures-0.4.64//:wasm_bindgen_futures\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__wasm-streams-0.5.0//:wasm_streams\", # wasm32-wasip1\n \"@crates_io__web-sys-0.3.91//:web_sys\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__cookie-0.18.1//:cookie\", # x86_64-pc-windows-msvc\n \"@crates_io__cookie_store-0.22.1//:cookie_store\", # x86_64-pc-windows-msvc\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\", # x86_64-pc-windows-msvc\n \"@crates_io__h2-0.4.13//:h2\", # x86_64-pc-windows-msvc\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\", # x86_64-pc-windows-msvc\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__mime-0.3.17//:mime\", # x86_64-pc-windows-msvc\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__rustls-0.23.37//:rustls\", # x86_64-pc-windows-msvc\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # x86_64-pc-windows-msvc\n \"@crates_io__rustls-platform-verifier-0.6.2//:rustls_platform_verifier\", # x86_64-pc-windows-msvc\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\", # x86_64-pc-windows-msvc\n \"@crates_io__tokio-util-0.7.18//:tokio_util\", # x86_64-pc-windows-msvc\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__cookie-0.18.1//:cookie\", # x86_64-unknown-linux-gnu\n \"@crates_io__cookie_store-0.22.1//:cookie_store\", # x86_64-unknown-linux-gnu\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\", # x86_64-unknown-linux-gnu\n \"@crates_io__h2-0.4.13//:h2\", # x86_64-unknown-linux-gnu\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\", # x86_64-unknown-linux-gnu\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__mime-0.3.17//:mime\", # x86_64-unknown-linux-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__rustls-0.23.37//:rustls\", # x86_64-unknown-linux-gnu\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # x86_64-unknown-linux-gnu\n \"@crates_io__rustls-platform-verifier-0.6.2//:rustls_platform_verifier\", # x86_64-unknown-linux-gnu\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\", # x86_64-unknown-linux-gnu\n \"@crates_io__tokio-util-0.7.18//:tokio_util\", # x86_64-unknown-linux-gnu\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__cookie-0.18.1//:cookie\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__cookie_store-0.22.1//:cookie_store\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__h2-0.4.13//:h2\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__http-body-1.0.1//:http_body\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__http-body-util-0.1.3//:http_body_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-1.8.1//:hyper\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__hyper-rustls-0.27.7//:hyper_rustls\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__hyper-util-0.1.20//:hyper_util\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__log-0.4.29//:log\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__mime-0.3.17//:mime\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__rustls-0.23.37//:rustls\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__rustls-platform-verifier-0.6.2//:rustls_platform_verifier\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-rustls-0.26.4//:tokio_rustls\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__tokio-util-0.7.18//:tokio_util\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__tower-0.5.3//:tower\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-http-0.6.8//:tower_http\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tower-service-0.3.3//:tower_service\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"//conditions:default\": [],\n }),\n aliases = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": {\n \"@crates_io__cookie-0.18.1//:cookie\": \"cookie_crate\", # aarch64-apple-darwin\n },\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": {\n \"@crates_io__cookie-0.18.1//:cookie\": \"cookie_crate\", # aarch64-unknown-linux-gnu\n },\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": {\n \"@crates_io__cookie-0.18.1//:cookie\": \"cookie_crate\", # x86_64-pc-windows-msvc\n },\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": {\n \"@crates_io__cookie-0.18.1//:cookie\": \"cookie_crate\", # x86_64-unknown-linux-gnu\n },\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": {\n \"@crates_io__cookie-0.18.1//:cookie\": \"cookie_crate\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n },\n \"//conditions:default\": {},\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__rustls\",\n \"__rustls-aws-lc-rs\",\n \"__tls\",\n \"brotli\",\n \"charset\",\n \"cookies\",\n \"default\",\n \"default-tls\",\n \"deflate\",\n \"gzip\",\n \"http2\",\n \"json\",\n \"query\",\n \"rustls\",\n \"stream\",\n \"system-proxy\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"zstd\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"zstd\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"zstd\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"zstd\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"zstd\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=reqwest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.2\",\n)\n" + } + }, + "crates_io__ring-0.17.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ring/0.17.14/download" + ], + "strip_prefix": "ring-0.17.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ring\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__getrandom-0.2.17//:getrandom\",\n \"@crates_io__ring-0.17.14//:build_script_build\",\n \"@crates_io__untrusted-0.9.0//:untrusted\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(all(target_arch = \"aarch64\", target_endian = \"little\"), target_vendor = \"apple\", any(target_os = \"ios\", target_os = \"macos\", target_os = \"tvos\", target_os = \"visionos\", target_os = \"watchos\")))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(any(all(target_arch = \"aarch64\", target_endian = \"little\"), all(target_arch = \"arm\", target_endian = \"little\")), any(target_os = \"android\", target_os = \"linux\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"dev_urandom_fallback\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.17.14\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"dev_urandom_fallback\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n ],\n edition = \"2021\",\n links = \"ring_core_0_17_14_\",\n pkg_name = \"ring\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ring\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.17.14\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__rsa-0.9.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rsa/0.9.10/download" + ], + "strip_prefix": "rsa-0.9.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rsa\",\n deps = [\n \"@crates_io__const-oid-0.9.6//:const_oid\",\n \"@crates_io__digest-0.10.7//:digest\",\n \"@crates_io__num-bigint-dig-0.8.6//:num_bigint_dig\",\n \"@crates_io__num-integer-0.1.46//:num_integer\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__pkcs1-0.7.5//:pkcs1\",\n \"@crates_io__pkcs8-0.10.2//:pkcs8\",\n \"@crates_io__rand_core-0.6.4//:rand_core\",\n \"@crates_io__signature-2.2.0//:signature\",\n \"@crates_io__spki-0.7.3//:spki\",\n \"@crates_io__subtle-2.6.1//:subtle\",\n \"@crates_io__zeroize-1.8.2//:zeroize\",\n ],\n aliases = {\n \"@crates_io__num-bigint-dig-0.8.6//:num_bigint_dig\": \"num_bigint\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rsa\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.10\",\n)\n" + } + }, + "crates_io__rustc-hash-2.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustc-hash/2.1.1/download" + ], + "strip_prefix": "rustc-hash-2.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustc_hash\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustc-hash\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.1.1\",\n)\n" + } + }, + "crates_io__rustc_version-0.4.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustc_version/0.4.1/download" + ], + "strip_prefix": "rustc_version-0.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustc_version\",\n deps = [\n \"@crates_io__semver-1.0.27//:semver\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustc_version\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.1\",\n)\n" + } + }, + "crates_io__rustix-0.38.44": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustix/0.38.44/download" + ], + "strip_prefix": "rustix-0.38.44", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustix\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__rustix-0.38.44//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__errno-0.3.14//:errno\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__linux-raw-sys-0.4.15//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__errno-0.3.14//:errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__errno-0.3.14//:errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n \"@crates_io__libc-0.2.183//:libc\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__errno-0.3.14//:errno\", # cfg(windows)\n \"@crates_io__windows-sys-0.59.0//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__linux-raw-sys-0.4.15//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__linux-raw-sys-0.4.15//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"//conditions:default\": [],\n }),\n aliases = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": {\n \"@crates_io__errno-0.3.14//:errno\": \"libc_errno\", # aarch64-apple-darwin, cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n },\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": {\n \"@crates_io__errno-0.3.14//:errno\": \"libc_errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))\n },\n \"@rules_rust//rust/platform:wasm32-wasip1\": {\n \"@crates_io__errno-0.3.14//:errno\": \"libc_errno\", # cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", target_arch = \"s390x\"), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))))), wasm32-wasip1\n },\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": {\n \"@crates_io__errno-0.3.14//:errno\": \"libc_errno\", # cfg(windows)\n },\n \"//conditions:default\": {},\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"fs\",\n \"libc-extra-traits\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.38.44\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"fs\",\n \"libc-extra-traits\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"rustix\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.38.44\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__rustix-1.1.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustix/1.1.4/download" + ], + "strip_prefix": "rustix-1.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustix\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__errno-0.3.14//:errno\",\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__rustix-1.1.4//:build_script_build\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__linux-raw-sys-0.12.1//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__linux-raw-sys-0.12.1//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__linux-raw-sys-0.12.1//:linux_raw_sys\", # cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))\n ],\n \"//conditions:default\": [],\n }),\n aliases = {\n \"@crates_io__errno-0.3.14//:errno\": \"libc_errno\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"fs\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"fs\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"rustix\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustix\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.1.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__rustls-0.23.37": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls/0.23.37/download" + ], + "strip_prefix": "rustls-0.23.37", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls\",\n deps = [\n \"@crates_io__aws-lc-rs-1.16.1//:aws_lc_rs\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__ring-0.17.14//:ring\",\n \"@crates_io__rustls-0.23.37//:build_script_build\",\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n \"@crates_io__rustls-webpki-0.103.9//:webpki\",\n \"@crates_io__subtle-2.6.1//:subtle\",\n \"@crates_io__zeroize-1.8.2//:zeroize\",\n ],\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"aws_lc_rs\",\n \"log\",\n \"logging\",\n \"ring\",\n \"std\",\n \"tls12\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.23.37\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"aws_lc_rs\",\n \"log\",\n \"logging\",\n \"ring\",\n \"std\",\n \"tls12\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__aws-lc-rs-1.16.1//:aws_lc_rs\",\n \"@crates_io__ring-0.17.14//:ring\",\n ],\n edition = \"2021\",\n pkg_name = \"rustls\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.23.37\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__rustls-native-certs-0.8.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-native-certs/0.8.3/download" + ], + "strip_prefix": "rustls-native-certs-0.8.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_native_certs\",\n deps = [\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__security-framework-3.7.0//:security_framework\", # cfg(target_os = \"macos\")\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__schannel-0.1.29//:schannel\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__openssl-probe-0.2.1//:openssl_probe\", # cfg(all(unix, not(target_os = \"macos\")))\n ],\n \"//conditions:default\": [],\n }),\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-native-certs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.3\",\n)\n" + } + }, + "crates_io__rustls-pki-types-1.14.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-pki-types/1.14.0/download" + ], + "strip_prefix": "rustls-pki-types-1.14.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_pki_types\",\n deps = [\n \"@crates_io__zeroize-1.8.2//:zeroize\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-pki-types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.14.0\",\n)\n" + } + }, + "crates_io__rustls-platform-verifier-0.6.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-platform-verifier/0.6.2/download" + ], + "strip_prefix": "rustls-platform-verifier-0.6.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_platform_verifier\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__rustls-0.23.37//:rustls\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__core-foundation-0.10.1//:core_foundation\", # cfg(any(target_vendor = \"apple\"))\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\", # cfg(any(target_vendor = \"apple\"))\n \"@crates_io__security-framework-3.7.0//:security_framework\", # cfg(any(target_vendor = \"apple\"))\n \"@crates_io__security-framework-sys-2.17.0//:security_framework_sys\", # cfg(any(target_vendor = \"apple\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__rustls-native-certs-0.8.3//:rustls_native_certs\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n \"@crates_io__rustls-webpki-0.103.9//:webpki\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__rustls-webpki-0.103.9//:webpki\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__webpki-root-certs-1.0.6//:webpki_root_certs\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__rustls-webpki-0.103.9//:webpki\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__webpki-root-certs-1.0.6//:webpki_root_certs\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__rustls-native-certs-0.8.3//:rustls_native_certs\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n \"@crates_io__rustls-webpki-0.103.9//:webpki\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__rustls-native-certs-0.8.3//:rustls_native_certs\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n \"@crates_io__rustls-webpki-0.103.9//:webpki\", # cfg(all(unix, not(target_os = \"android\"), not(target_vendor = \"apple\"), not(target_arch = \"wasm32\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-platform-verifier\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.2\",\n)\n" + } + }, + "crates_io__rustls-platform-verifier-android-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-platform-verifier-android/0.1.1/download" + ], + "strip_prefix": "rustls-platform-verifier-android-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"rustls_platform_verifier_android\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-platform-verifier-android\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__rustls-webpki-0.103.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustls-webpki/0.103.9/download" + ], + "strip_prefix": "rustls-webpki-0.103.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"webpki\",\n deps = [\n \"@crates_io__aws-lc-rs-1.16.1//:aws_lc_rs\",\n \"@crates_io__ring-0.17.14//:ring\",\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n \"@crates_io__untrusted-0.9.0//:untrusted\",\n ],\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"aws-lc-rs\",\n \"ring\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustls-webpki\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.103.9\",\n)\n" + } + }, + "crates_io__rustversion-1.0.22": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustversion/1.0.22/download" + ], + "strip_prefix": "rustversion-1.0.22", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"rustversion\",\n deps = [\n \"@crates_io__rustversion-1.0.22//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.22\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build/build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"rustversion\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.22\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__ryu-1.0.23": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ryu/1.0.23/download" + ], + "strip_prefix": "ryu-1.0.23", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ryu\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ryu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.23\",\n)\n" + } + }, + "crates_io__safe_arch-0.7.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/safe_arch/0.7.4/download" + ], + "strip_prefix": "safe_arch-0.7.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"safe_arch\",\n deps = [\n \"@crates_io__bytemuck-1.25.0//:bytemuck\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bytemuck\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=safe_arch\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.4\",\n)\n" + } + }, + "crates_io__same-file-1.0.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/same-file/1.0.6/download" + ], + "strip_prefix": "same-file-1.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"same_file\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__winapi-util-0.1.11//:winapi_util\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=same-file\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6\",\n)\n" + } + }, + "crates_io__schannel-0.1.29": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/schannel/0.1.29/download" + ], + "strip_prefix": "schannel-0.1.29", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"schannel\",\n deps = [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=schannel\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.29\",\n)\n" + } + }, + "crates_io__scoped-tls-1.0.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/scoped-tls/1.0.1/download" + ], + "strip_prefix": "scoped-tls-1.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"scoped_tls\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=scoped-tls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.1\",\n)\n" + } + }, + "crates_io__scopeguard-1.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/scopeguard/1.2.0/download" + ], + "strip_prefix": "scopeguard-1.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"scopeguard\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=scopeguard\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.0\",\n)\n" + } + }, + "crates_io__security-framework-3.7.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/security-framework/3.7.0/download" + ], + "strip_prefix": "security-framework-3.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"security_framework\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__core-foundation-0.10.1//:core_foundation\",\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__security-framework-sys-2.17.0//:security_framework_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"OSX_10_14\",\n \"alpn\",\n \"default\",\n \"session-tickets\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=security-framework\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.7.0\",\n)\n" + } + }, + "crates_io__security-framework-sys-2.17.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/security-framework-sys/2.17.0/download" + ], + "strip_prefix": "security-framework-sys-2.17.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"security_framework_sys\",\n deps = [\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@crates_io__libc-0.2.183//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"OSX_10_13\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=security-framework-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.17.0\",\n)\n" + } + }, + "crates_io__selectors-0.25.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/selectors/0.25.0/download" + ], + "strip_prefix": "selectors-0.25.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"selectors\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__cssparser-0.31.2//:cssparser\",\n \"@crates_io__fxhash-0.2.1//:fxhash\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__new_debug_unreachable-1.0.6//:debug_unreachable\",\n \"@crates_io__phf-0.10.1//:phf\",\n \"@crates_io__precomputed-hash-0.1.1//:precomputed_hash\",\n \"@crates_io__selectors-0.25.0//:build_script_build\",\n \"@crates_io__servo_arc-0.3.0//:servo_arc\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ],\n proc_macro_deps = [\n \"@crates_io__derive_more-0.99.20//:derive_more\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=selectors\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.25.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.10.0//:phf_codegen\",\n ],\n edition = \"2015\",\n pkg_name = \"selectors\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=selectors\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.25.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__selectors-0.33.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/selectors/0.33.0/download" + ], + "strip_prefix": "selectors-0.33.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"selectors\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__cssparser-0.36.0//:cssparser\",\n \"@crates_io__derive_more-2.1.1//:derive_more\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__new_debug_unreachable-1.0.6//:debug_unreachable\",\n \"@crates_io__phf-0.13.1//:phf\",\n \"@crates_io__precomputed-hash-0.1.1//:precomputed_hash\",\n \"@crates_io__rustc-hash-2.1.1//:rustc_hash\",\n \"@crates_io__selectors-0.33.0//:build_script_build\",\n \"@crates_io__servo_arc-0.4.3//:servo_arc\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=selectors\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.33.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.13.1//:phf_codegen\",\n ],\n edition = \"2021\",\n pkg_name = \"selectors\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=selectors\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.33.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__semver-1.0.27": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/semver/1.0.27/download" + ], + "strip_prefix": "semver-1.0.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"semver\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=semver\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.27\",\n)\n" + } + }, + "crates_io__serde-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde/1.0.228/download" + ], + "strip_prefix": "serde-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde\",\n deps = [\n \"@crates_io__serde-1.0.228//:build_script_build\",\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n ],\n proc_macro_deps = [\n \"@crates_io__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"derive\",\n \"rc\",\n \"serde_derive\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"derive\",\n \"rc\",\n \"serde_derive\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__serde_core-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_core/1.0.228/download" + ], + "strip_prefix": "serde_core-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_core\",\n deps = [\n \"@crates_io__serde_core-1.0.228//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"rc\",\n \"result\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"rc\",\n \"result\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_core\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.228\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__serde_derive-1.0.228": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_derive/1.0.228/download" + ], + "strip_prefix": "serde_derive-1.0.228", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"serde_derive\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.228\",\n)\n" + } + }, + "crates_io__serde_json-1.0.149": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_json/1.0.149/download" + ], + "strip_prefix": "serde_json-1.0.149", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_json\",\n deps = [\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n \"@crates_io__serde_json-1.0.149//:build_script_build\",\n \"@crates_io__zmij-1.0.21//:zmij\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"raw_value\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.149\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"raw_value\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"serde_json\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_json\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.149\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__serde_regex-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_regex/1.1.0/download" + ], + "strip_prefix": "serde_regex-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_regex\",\n deps = [\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_regex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__serde_spanned-1.0.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_spanned/1.0.4/download" + ], + "strip_prefix": "serde_spanned-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_spanned\",\n deps = [\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_spanned\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "crates_io__serde_urlencoded-0.7.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/serde_urlencoded/0.7.1/download" + ], + "strip_prefix": "serde_urlencoded-0.7.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"serde_urlencoded\",\n deps = [\n \"@crates_io__form_urlencoded-1.2.2//:form_urlencoded\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__ryu-1.0.23//:ryu\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=serde_urlencoded\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.1\",\n)\n" + } + }, + "crates_io__servo_arc-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/servo_arc/0.3.0/download" + ], + "strip_prefix": "servo_arc-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"servo_arc\",\n deps = [\n \"@crates_io__stable_deref_trait-1.2.1//:stable_deref_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=servo_arc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "crates_io__servo_arc-0.4.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/servo_arc/0.4.3/download" + ], + "strip_prefix": "servo_arc-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"servo_arc\",\n deps = [\n \"@crates_io__stable_deref_trait-1.2.1//:stable_deref_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"track_alloc_size\",\n ],\n crate_root = \"lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=servo_arc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n" + } + }, + "crates_io__sha1-0.10.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sha1/0.10.6/download" + ], + "strip_prefix": "sha1-0.10.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sha1\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__digest-0.10.7//:digest\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86\", target_arch = \"x86_64\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sha1\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.6\",\n)\n" + } + }, + "crates_io__sha2-0.10.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sha2/0.10.9/download" + ], + "strip_prefix": "sha2-0.10.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sha2\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__digest-0.10.7//:digest\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__cpufeatures-0.2.17//:cpufeatures\", # cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n \"std\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n \"std\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n \"std\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n \"std\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"std\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sha2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.10.9\",\n)\n" + } + }, + "crates_io__sharded-slab-0.1.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sharded-slab/0.1.7/download" + ], + "strip_prefix": "sharded-slab-0.1.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sharded_slab\",\n deps = [\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sharded-slab\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.7\",\n)\n" + } + }, + "crates_io__shlex-1.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/shlex/1.3.0/download" + ], + "strip_prefix": "shlex-1.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"shlex\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=shlex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.3.0\",\n)\n" + } + }, + "crates_io__signal-hook-registry-1.4.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/signal-hook-registry/1.4.8/download" + ], + "strip_prefix": "signal-hook-registry-1.4.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"signal_hook_registry\",\n deps = [\n \"@crates_io__errno-0.3.14//:errno\",\n \"@crates_io__libc-0.2.183//:libc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=signal-hook-registry\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.4.8\",\n)\n" + } + }, + "crates_io__signature-2.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/signature/2.2.0/download" + ], + "strip_prefix": "signature-2.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"signature\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=signature\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.2.0\",\n)\n" + } + }, + "crates_io__simba-0.9.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/simba/0.9.1/download" + ], + "strip_prefix": "simba-0.9.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"simba\",\n deps = [\n \"@crates_io__approx-0.5.1//:approx\",\n \"@crates_io__num-complex-0.4.6//:num_complex\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__wide-0.7.33//:wide\",\n ],\n proc_macro_deps = [\n \"@crates_io__paste-1.0.15//:paste\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n \"wide\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=simba\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.1\",\n)\n" + } + }, + "crates_io__simd-adler32-0.3.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/simd-adler32/0.3.8/download" + ], + "strip_prefix": "simd-adler32-0.3.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"simd_adler32\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=simd-adler32\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.8\",\n)\n" + } + }, + "crates_io__siphasher-0.3.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/siphasher/0.3.11/download" + ], + "strip_prefix": "siphasher-0.3.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"siphasher\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=siphasher\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.11\",\n)\n" + } + }, + "crates_io__siphasher-1.0.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/siphasher/1.0.2/download" + ], + "strip_prefix": "siphasher-1.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"siphasher\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=siphasher\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2\",\n)\n" + } + }, + "crates_io__slab-0.4.12": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/slab/0.4.12/download" + ], + "strip_prefix": "slab-0.4.12", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"slab\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=slab\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.12\",\n)\n" + } + }, + "crates_io__smallvec-1.15.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/smallvec/1.15.1/download" + ], + "strip_prefix": "smallvec-1.15.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"smallvec\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"const_generics\",\n \"const_new\",\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=smallvec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.15.1\",\n)\n" + } + }, + "crates_io__socket2-0.5.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/socket2/0.5.10/download" + ], + "strip_prefix": "socket2-0.5.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"socket2\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.52.0//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(unix)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"all\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=socket2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.10\",\n)\n" + } + }, + "crates_io__socket2-0.6.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/socket2/0.6.3/download" + ], + "strip_prefix": "socket2-0.6.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"socket2\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"all\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=socket2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.3\",\n)\n" + } + }, + "crates_io__spider-2.47.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d770e1b23232fa0af8aea8d95043dab6bcd730330042f656afa2a28f9fef6d46", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider/2.47.18/download" + ], + "strip_prefix": "spider-2.47.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider\",\n deps = [\n \"@crates_io__ahash-0.8.12//:ahash\",\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__case_insensitive_string-0.2.10//:case_insensitive_string\",\n \"@crates_io__h2-0.4.13//:h2\",\n \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__lol_html-2.7.2//:lol_html\",\n \"@crates_io__num_cpus-1.17.0//:num_cpus\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__quick-xml-0.39.2//:quick_xml\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__serde_regex-1.1.0//:serde_regex\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__spider_agent_types-2.47.18//:spider_agent_types\",\n \"@crates_io__spider_fingerprint-2.38.1//:spider_fingerprint\",\n \"@crates_io__string-interner-0.19.0//:string_interner\",\n \"@crates_io__string_concat-0.0.1//:string_concat\",\n \"@crates_io__strum-0.27.2//:strum\",\n \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n \"@crates_io__tower-0.5.3//:tower\",\n \"@crates_io__url-2.5.8//:url\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_os = \"linux\")\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(target_arch = \"wasm32\")\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(target_arch = \"wasm32\")\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_os = \"linux\")\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(target_os = \"linux\")\n \"@crates_io__reqwest-0.13.2//:reqwest\", # cfg(not(target_arch = \"wasm32\"))\n \"@crates_io__tokio-1.50.0//:tokio\", # cfg(not(target_arch = \"wasm32\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"reqwest_json\",\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.47.18\",\n)\n" + } + }, + "crates_io__spider_agent_types-2.47.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7812544c380670f38e7fd78c5f0eb8ceb6c5fb3b291aafbc6173ff4022f8df7f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_agent_types/2.47.18/download" + ], + "strip_prefix": "spider_agent_types-2.47.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider_agent_types\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__llm_models_spider-0.1.42//:llm_models_spider\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_agent_types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.47.18\",\n)\n" + } + }, + "crates_io__spider_chromiumoxide_cdp-0.7.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "425d07927c18fd157c4680cee8c7868e4a9bf4723bda1a4951c3316e3abdea4b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_chromiumoxide_cdp/0.7.9/download" + ], + "strip_prefix": "spider_chromiumoxide_cdp-0.7.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chromiumoxide_cdp\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__spider_chromiumoxide_pdl-0.7.6//:chromiumoxide_pdl\",\n \"@crates_io__spider_chromiumoxide_types-0.7.5//:chromiumoxide_types\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_chromiumoxide_cdp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.9\",\n)\n" + } + }, + "crates_io__spider_chromiumoxide_pdl-0.7.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4090793264cfc7533186505f7cc2198ef03325dfcbfe32d0e6814c6ca759051f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_chromiumoxide_pdl/0.7.6/download" + ], + "strip_prefix": "spider_chromiumoxide_pdl-0.7.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chromiumoxide_pdl\",\n deps = [\n \"@crates_io__either-1.15.0//:either\",\n \"@crates_io__heck-0.4.1//:heck\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__spider_chromiumoxide_types-0.7.5//:chromiumoxide_types\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_chromiumoxide_pdl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.6\",\n)\n" + } + }, + "crates_io__spider_chromiumoxide_types-0.7.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dec8ac95a73fa31063b306dcbb48a2b3115fde2ff9152004148eb152844917e9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_chromiumoxide_types/0.7.5/download" + ], + "strip_prefix": "spider_chromiumoxide_types-0.7.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"chromiumoxide_types\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_chromiumoxide_types\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.5\",\n)\n" + } + }, + "crates_io__spider_fingerprint-2.38.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f4e26862e5b31d1292804e64d59601af1594b1ab6e0474c2b62dcd447497ae89", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_fingerprint/2.38.1/download" + ], + "strip_prefix": "spider_fingerprint-2.38.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider_fingerprint\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__case_insensitive_string-0.2.10//:case_insensitive_string\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__rand-0.9.2//:rand\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__spider_fingerprint-2.38.1//:build_script_build\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"dynamic-versions\",\n \"headers\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_fingerprint\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.38.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"dynamic-versions\",\n \"headers\",\n \"serde\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n \"@crates_io__reqwest-0.12.28//:reqwest\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n ],\n edition = \"2021\",\n pkg_name = \"spider_fingerprint\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_fingerprint\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.38.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__spider_network_blocker-0.0.58": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "19cf37feaf9b5f9202d060f8cebc213c2833aecd462841749ab3d4d39d1db3ba", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_network_blocker/0.0.58/download" + ], + "strip_prefix": "spider_network_blocker-0.0.58", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider_network_blocker\",\n deps = [\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__spider_network_blocker-0.0.58//:build_script_build\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"hashbrown\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_network_blocker\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.58\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"hashbrown\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__convert_case-0.8.0//:convert_case\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n ],\n edition = \"2021\",\n pkg_name = \"spider_network_blocker\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_network_blocker\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.0.58\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__spider_scraper-0.1.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "323b02db860415a5414ac0a55a10e830df133b7f3cd877178e5889bc9fba01d3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_scraper/0.1.2/download" + ], + "strip_prefix": "spider_scraper-0.1.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"scraper\",\n deps = [\n \"@crates_io__ahash-0.8.12//:ahash\",\n \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n \"@crates_io__cssparser-0.31.2//:cssparser\",\n \"@crates_io__ego-tree-0.10.0//:ego_tree\",\n \"@crates_io__fast_html5ever-0.26.6//:fast_html5ever\",\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__selectors-0.25.0//:selectors\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_scraper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.2\",\n)\n" + } + }, + "crates_io__spider_transformations-2.37.115": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c23d3c922058c56970357725d8ded379188314cbe99fb1611531542f170dbcb0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_transformations/2.37.115/download" + ], + "strip_prefix": "spider_transformations-2.37.115", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider_transformations\",\n deps = [\n \"@crates_io__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates_io__auto_encoder-0.1.10//:auto_encoder\",\n \"@crates_io__fast_html2md-0.0.58//:html2md\",\n \"@crates_io__html5ever-0.27.0//:html5ever\",\n \"@crates_io__indexmap-1.9.3//:indexmap\",\n \"@crates_io__llm_readability-0.0.13//:llm_readability\",\n \"@crates_io__lol_html-2.7.2//:lol_html\",\n \"@crates_io__markup5ever-0.12.1//:markup5ever\",\n \"@crates_io__phf-0.11.3//:phf\",\n \"@crates_io__phf_codegen-0.11.3//:phf_codegen\",\n \"@crates_io__regex-1.12.3//:regex\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__spider-2.47.18//:spider\",\n \"@crates_io__spider_scraper-0.1.2//:scraper\",\n \"@crates_io__tendril-0.4.3//:tendril\",\n \"@crates_io__thiserror-1.0.69//:thiserror\",\n \"@crates_io__unicode-width-0.2.2//:unicode_width\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"indexmap\",\n \"serde\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_transformations\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.37.115\",\n)\n" + } + }, + "crates_io__spider_utils-2.47.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8f0f3b2babd77b2232899c20b044111fae13eec6af4445e5b51727a35f9aec75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spider_utils/2.47.18/download" + ], + "strip_prefix": "spider_utils-2.47.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spider_utils\",\n deps = [\n \"@crates_io__hashbrown-0.16.1//:hashbrown\",\n \"@crates_io__lazy_static-1.5.0//:lazy_static\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__spider_scraper-0.1.2//:scraper\",\n \"@crates_io__sxd-document-0.3.2//:sxd_document\",\n \"@crates_io__sxd-xpath-0.4.2//:sxd_xpath\",\n \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spider_utils\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.47.18\",\n)\n" + } + }, + "crates_io__spin-0.9.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spin/0.9.8/download" + ], + "strip_prefix": "spin-0.9.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spin\",\n deps = [\n \"@crates_io__lock_api-0.4.14//:lock_api\",\n ],\n aliases = {\n \"@crates_io__lock_api-0.4.14//:lock_api\": \"lock_api_crate\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"barrier\",\n \"default\",\n \"lazy\",\n \"lock_api\",\n \"lock_api_crate\",\n \"mutex\",\n \"once\",\n \"rwlock\",\n \"spin_mutex\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spin\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.8\",\n)\n" + } + }, + "crates_io__spki-0.7.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/spki/0.7.3/download" + ], + "strip_prefix": "spki-0.7.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"spki\",\n deps = [\n \"@crates_io__der-0.7.10//:der\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=spki\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.3\",\n)\n" + } + }, + "crates_io__sqlx-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx/0.8.6/download" + ], + "strip_prefix": "sqlx-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx\",\n deps = [\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__sqlx-sqlite-0.8.6//:sqlx_sqlite\",\n ],\n proc_macro_deps = [\n \"@crates_io__sqlx-macros-0.8.6//:sqlx_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"_rt-tokio\",\n \"_sqlite\",\n \"any\",\n \"default\",\n \"derive\",\n \"json\",\n \"macros\",\n \"migrate\",\n \"runtime-tokio\",\n \"runtime-tokio-native-tls\",\n \"sqlite\",\n \"sqlx-macros\",\n \"sqlx-sqlite\",\n \"tls-native-tls\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-core-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-core/0.8.6/download" + ], + "strip_prefix": "sqlx-core-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx_core\",\n deps = [\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__crc-3.4.0//:crc\",\n \"@crates_io__crossbeam-queue-0.3.12//:crossbeam_queue\",\n \"@crates_io__either-1.15.0//:either\",\n \"@crates_io__event-listener-5.4.1//:event_listener\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-intrusive-0.5.0//:futures_intrusive\",\n \"@crates_io__futures-io-0.3.32//:futures_io\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__hashlink-0.10.0//:hashlink\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__native-tls-0.2.18//:native_tls\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__sha2-0.10.9//:sha2\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"_rt-tokio\",\n \"_tls-native-tls\",\n \"any\",\n \"crc\",\n \"default\",\n \"json\",\n \"migrate\",\n \"native-tls\",\n \"offline\",\n \"serde\",\n \"serde_json\",\n \"sha2\",\n \"tokio\",\n \"tokio-stream\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-macros-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-macros/0.8.6/download" + ], + "strip_prefix": "sqlx-macros-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"sqlx_macros\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__sqlx-macros-core-0.8.6//:sqlx_macros_core\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"_rt-tokio\",\n \"_tls-native-tls\",\n \"default\",\n \"derive\",\n \"json\",\n \"macros\",\n \"migrate\",\n \"sqlite\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-macros-core-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-macros-core/0.8.6/download" + ], + "strip_prefix": "sqlx-macros-core-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx_macros_core\",\n deps = [\n \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n \"@crates_io__either-1.15.0//:either\",\n \"@crates_io__heck-0.5.0//:heck\",\n \"@crates_io__hex-0.4.3//:hex\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__sha2-0.10.9//:sha2\",\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__sqlx-sqlite-0.8.6//:sqlx_sqlite\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"_rt-tokio\",\n \"_sqlite\",\n \"_tls-native-tls\",\n \"default\",\n \"derive\",\n \"json\",\n \"macros\",\n \"migrate\",\n \"sqlite\",\n \"sqlx-sqlite\",\n \"tokio\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-macros-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-mysql-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-mysql/0.8.6/download" + ], + "strip_prefix": "sqlx-mysql-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx_mysql\",\n deps = [\n \"@crates_io__atoi-2.0.0//:atoi\",\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__crc-3.4.0//:crc\",\n \"@crates_io__digest-0.10.7//:digest\",\n \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n \"@crates_io__either-1.15.0//:either\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-io-0.3.32//:futures_io\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__generic-array-0.14.7//:generic_array\",\n \"@crates_io__hex-0.4.3//:hex\",\n \"@crates_io__hkdf-0.12.4//:hkdf\",\n \"@crates_io__hmac-0.12.1//:hmac\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__md-5-0.10.6//:md5\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__rand-0.8.5//:rand\",\n \"@crates_io__rsa-0.9.10//:rsa\",\n \"@crates_io__sha1-0.10.6//:sha1\",\n \"@crates_io__sha2-0.10.9//:sha2\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__stringprep-0.1.5//:stringprep\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__whoami-1.6.1//:whoami\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-mysql\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-postgres-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-postgres/0.8.6/download" + ], + "strip_prefix": "sqlx-postgres-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx_postgres\",\n deps = [\n \"@crates_io__atoi-2.0.0//:atoi\",\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n \"@crates_io__crc-3.4.0//:crc\",\n \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__hex-0.4.3//:hex\",\n \"@crates_io__hkdf-0.12.4//:hkdf\",\n \"@crates_io__hmac-0.12.1//:hmac\",\n \"@crates_io__home-0.5.12//:home\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__md-5-0.10.6//:md5\",\n \"@crates_io__memchr-2.8.0//:memchr\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__rand-0.8.5//:rand\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__sha2-0.10.9//:sha2\",\n \"@crates_io__smallvec-1.15.1//:smallvec\",\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__stringprep-0.1.5//:stringprep\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__whoami-1.6.1//:whoami\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__etcetera-0.8.0//:etcetera\", # cfg(target_os = \"windows\")\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-postgres\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__sqlx-sqlite-0.8.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sqlx-sqlite/0.8.6/download" + ], + "strip_prefix": "sqlx-sqlite-0.8.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sqlx_sqlite\",\n deps = [\n \"@crates_io__atoi-2.0.0//:atoi\",\n \"@crates_io__flume-0.11.1//:flume\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-executor-0.3.32//:futures_executor\",\n \"@crates_io__futures-intrusive-0.5.0//:futures_intrusive\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__libsqlite3-sys-0.30.1//:libsqlite3_sys\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_urlencoded-0.7.1//:serde_urlencoded\",\n \"@crates_io__sqlx-core-0.8.6//:sqlx_core\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__url-2.5.8//:url\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"any\",\n \"bundled\",\n \"json\",\n \"migrate\",\n \"serde\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"offline\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"offline\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"offline\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"offline\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"offline\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sqlx-sqlite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.6\",\n)\n" + } + }, + "crates_io__stable_deref_trait-1.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/stable_deref_trait/1.2.1/download" + ], + "strip_prefix": "stable_deref_trait-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"stable_deref_trait\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=stable_deref_trait\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "crates_io__static_assertions-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/static_assertions/1.1.0/download" + ], + "strip_prefix": "static_assertions-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"static_assertions\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=static_assertions\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__statrs-0.18.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/statrs/0.18.0/download" + ], + "strip_prefix": "statrs-0.18.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"statrs\",\n deps = [\n \"@crates_io__approx-0.5.1//:approx\",\n \"@crates_io__nalgebra-0.33.2//:nalgebra\",\n \"@crates_io__num-traits-0.2.19//:num_traits\",\n \"@crates_io__rand-0.8.5//:rand\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"nalgebra\",\n \"rand\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=statrs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.18.0\",\n)\n" + } + }, + "crates_io__string-interner-0.19.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/string-interner/0.19.0/download" + ], + "strip_prefix": "string-interner-0.19.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"string_interner\",\n deps = [\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"backends\",\n \"inline-more\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=string-interner\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.19.0\",\n)\n" + } + }, + "crates_io__string_cache-0.8.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/string_cache/0.8.9/download" + ], + "strip_prefix": "string_cache-0.8.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"string_cache\",\n deps = [\n \"@crates_io__new_debug_unreachable-1.0.6//:debug_unreachable\",\n \"@crates_io__parking_lot-0.12.5//:parking_lot\",\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n \"@crates_io__precomputed-hash-0.1.1//:precomputed_hash\",\n \"@crates_io__serde-1.0.228//:serde\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"serde\",\n \"serde_support\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=string_cache\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.9\",\n)\n" + } + }, + "crates_io__string_cache_codegen-0.5.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/string_cache_codegen/0.5.4/download" + ], + "strip_prefix": "string_cache_codegen-0.5.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"string_cache_codegen\",\n deps = [\n \"@crates_io__phf_generator-0.11.3//:phf_generator\",\n \"@crates_io__phf_shared-0.11.3//:phf_shared\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=string_cache_codegen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.4\",\n)\n" + } + }, + "crates_io__string_concat-0.0.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c3c3ee6129eec20fed59acf2e9cfb3ffd20d0bbe39fe334c22af0edc56dfe752", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/string_concat/0.0.1/download" + ], + "strip_prefix": "string_concat-0.0.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"string_concat\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=string_concat\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.1\",\n)\n" + } + }, + "crates_io__stringprep-0.1.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/stringprep/0.1.5/download" + ], + "strip_prefix": "stringprep-0.1.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"stringprep\",\n deps = [\n \"@crates_io__unicode-bidi-0.3.18//:unicode_bidi\",\n \"@crates_io__unicode-normalization-0.1.25//:unicode_normalization\",\n \"@crates_io__unicode-properties-0.1.4//:unicode_properties\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=stringprep\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.5\",\n)\n" + } + }, + "crates_io__strsim-0.11.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/strsim/0.11.1/download" + ], + "strip_prefix": "strsim-0.11.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"strsim\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=strsim\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1\",\n)\n" + } + }, + "crates_io__strum-0.27.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/strum/0.27.2/download" + ], + "strip_prefix": "strum-0.27.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"strum\",\n proc_macro_deps = [\n \"@crates_io__strum_macros-0.27.2//:strum_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"derive\",\n \"std\",\n \"strum_macros\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=strum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.27.2\",\n)\n" + } + }, + "crates_io__strum_macros-0.27.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/strum_macros/0.27.2/download" + ], + "strip_prefix": "strum_macros-0.27.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"strum_macros\",\n deps = [\n \"@crates_io__heck-0.5.0//:heck\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=strum_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.27.2\",\n)\n" + } + }, + "crates_io__subtle-2.6.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/subtle/2.6.1/download" + ], + "strip_prefix": "subtle-2.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"subtle\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=subtle\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.6.1\",\n)\n" + } + }, + "crates_io__sxd-document-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sxd-document/0.3.2/download" + ], + "strip_prefix": "sxd-document-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sxd_document\",\n deps = [\n \"@crates_io__peresil-0.3.0//:peresil\",\n \"@crates_io__typed-arena-1.7.0//:typed_arena\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sxd-document\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__sxd-xpath-0.4.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sxd-xpath/0.4.2/download" + ], + "strip_prefix": "sxd-xpath-0.4.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sxd_xpath\",\n deps = [\n \"@crates_io__peresil-0.3.0//:peresil\",\n \"@crates_io__quick-error-1.2.3//:quick_error\",\n \"@crates_io__sxd-document-0.3.2//:sxd_document\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sxd-xpath\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.2\",\n)\n" + } + }, + "crates_io__syn-1.0.109": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/syn/1.0.109/download" + ], + "strip_prefix": "syn-1.0.109", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syn\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-1.0.109//:build_script_build\",\n \"@crates_io__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"default\",\n \"derive\",\n \"extra-traits\",\n \"fold\",\n \"full\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n \"quote\",\n \"visit\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.109\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"default\",\n \"derive\",\n \"extra-traits\",\n \"fold\",\n \"full\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n \"quote\",\n \"visit\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"syn\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.109\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__syn-2.0.117": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/syn/2.0.117/download" + ], + "strip_prefix": "syn-2.0.117", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syn\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"default\",\n \"derive\",\n \"extra-traits\",\n \"fold\",\n \"full\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n \"visit\",\n \"visit-mut\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.117\",\n)\n" + } + }, + "crates_io__sync_wrapper-1.0.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sync_wrapper/1.0.2/download" + ], + "strip_prefix": "sync_wrapper-1.0.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sync_wrapper\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"futures\",\n \"futures-core\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sync_wrapper\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2\",\n)\n" + } + }, + "crates_io__synstructure-0.12.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/synstructure/0.12.6/download" + ], + "strip_prefix": "synstructure-0.12.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"synstructure\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-1.0.109//:syn\",\n \"@crates_io__unicode-xid-0.2.6//:unicode_xid\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=synstructure\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.6\",\n)\n" + } + }, + "crates_io__synstructure-0.13.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/synstructure/0.13.2/download" + ], + "strip_prefix": "synstructure-0.13.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"synstructure\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=synstructure\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.2\",\n)\n" + } + }, + "crates_io__sysinfo-0.38.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/sysinfo/0.38.4/download" + ], + "strip_prefix": "sysinfo-0.38.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"sysinfo\",\n deps = [\n \"@crates_io__memchr-2.8.0//:memchr\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))\n \"@crates_io__objc2-core-foundation-0.3.2//:objc2_core_foundation\", # aarch64-apple-darwin\n \"@crates_io__objc2-io-kit-0.3.2//:objc2_io_kit\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))\n \"@crates_io__ntapi-0.4.3//:ntapi\", # x86_64-pc-windows-msvc\n \"@crates_io__windows-0.62.2//:windows\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"objc2-io-kit\",\n \"system\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"objc2-core-foundation\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"windows\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=sysinfo\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.38.4\",\n)\n" + } + }, + "crates_io__system-configuration-0.7.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/system-configuration/0.7.0/download" + ], + "strip_prefix": "system-configuration-0.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"system_configuration\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__core-foundation-0.9.4//:core_foundation\",\n \"@crates_io__system-configuration-sys-0.6.0//:system_configuration_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=system-configuration\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.0\",\n)\n" + } + }, + "crates_io__system-configuration-sys-0.6.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/system-configuration-sys/0.6.0/download" + ], + "strip_prefix": "system-configuration-sys-0.6.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"system_configuration_sys\",\n deps = [\n \"@crates_io__core-foundation-sys-0.8.7//:core_foundation_sys\",\n \"@crates_io__libc-0.2.183//:libc\",\n \"@crates_io__system-configuration-sys-0.6.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=system-configuration-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"system-configuration-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=system-configuration-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.6.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__tempfile-3.27.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tempfile/3.27.0/download" + ], + "strip_prefix": "tempfile-3.27.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tempfile\",\n deps = [\n \"@crates_io__fastrand-2.3.0//:fastrand\",\n \"@crates_io__getrandom-0.4.2//:getrandom\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__rustix-1.1.4//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__rustix-1.1.4//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__rustix-1.1.4//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__rustix-1.1.4//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__rustix-1.1.4//:rustix\", # cfg(any(unix, target_os = \"wasi\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"getrandom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tempfile\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"3.27.0\",\n)\n" + } + }, + "crates_io__tendril-0.4.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tendril/0.4.3/download" + ], + "strip_prefix": "tendril-0.4.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tendril\",\n deps = [\n \"@crates_io__futf-0.1.5//:futf\",\n \"@crates_io__mac-0.1.1//:mac\",\n \"@crates_io__utf-8-0.7.6//:utf8\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tendril\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.3\",\n)\n" + } + }, + "crates_io__thiserror-1.0.69": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror/1.0.69/download" + ], + "strip_prefix": "thiserror-1.0.69", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"thiserror\",\n deps = [\n \"@crates_io__thiserror-1.0.69//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@crates_io__thiserror-impl-1.0.69//:thiserror_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.69\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"thiserror\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.69\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__thiserror-2.0.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror/2.0.18/download" + ], + "strip_prefix": "thiserror-2.0.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"thiserror\",\n deps = [\n \"@crates_io__thiserror-2.0.18//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@crates_io__thiserror-impl-2.0.18//:thiserror_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.18\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"thiserror\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.0.18\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__thiserror-impl-1.0.69": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror-impl/1.0.69/download" + ], + "strip_prefix": "thiserror-impl-1.0.69", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"thiserror_impl\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.69\",\n)\n" + } + }, + "crates_io__thiserror-impl-2.0.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thiserror-impl/2.0.18/download" + ], + "strip_prefix": "thiserror-impl-2.0.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"thiserror_impl\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thiserror-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.18\",\n)\n" + } + }, + "crates_io__thread_local-1.1.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/thread_local/1.1.9/download" + ], + "strip_prefix": "thread_local-1.1.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"thread_local\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=thread_local\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.9\",\n)\n" + } + }, + "crates_io__time-0.3.47": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/time/0.3.47/download" + ], + "strip_prefix": "time-0.3.47", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"time\",\n deps = [\n \"@crates_io__deranged-0.5.8//:deranged\",\n \"@crates_io__itoa-1.0.17//:itoa\",\n \"@crates_io__num-conv-0.2.0//:num_conv\",\n \"@crates_io__powerfmt-0.2.0//:powerfmt\",\n \"@crates_io__time-core-0.1.8//:time_core\",\n ],\n proc_macro_deps = [\n \"@crates_io__time-macros-0.2.27//:time_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"formatting\",\n \"macros\",\n \"parsing\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=time\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.47\",\n)\n" + } + }, + "crates_io__time-core-0.1.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/time-core/0.1.8/download" + ], + "strip_prefix": "time-core-0.1.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"time_core\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=time-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.8\",\n)\n" + } + }, + "crates_io__time-macros-0.2.27": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/time-macros/0.2.27/download" + ], + "strip_prefix": "time-macros-0.2.27", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"time_macros\",\n deps = [\n \"@crates_io__num-conv-0.2.0//:num_conv\",\n \"@crates_io__time-core-0.1.8//:time_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"formatting\",\n \"parsing\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=time-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.27\",\n)\n" + } + }, + "crates_io__tinystr-0.8.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinystr/0.8.2/download" + ], + "strip_prefix": "tinystr-0.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinystr\",\n deps = [\n \"@crates_io__zerovec-0.11.5//:zerovec\",\n ],\n proc_macro_deps = [\n \"@crates_io__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"zerovec\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinystr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.2\",\n)\n" + } + }, + "crates_io__tinytemplate-1.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinytemplate/1.2.1/download" + ], + "strip_prefix": "tinytemplate-1.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinytemplate\",\n deps = [\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinytemplate\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.2.1\",\n)\n" + } + }, + "crates_io__tinyvec-1.10.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinyvec/1.10.0/download" + ], + "strip_prefix": "tinyvec-1.10.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinyvec\",\n deps = [\n \"@crates_io__tinyvec_macros-0.1.1//:tinyvec_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"tinyvec_macros\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinyvec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.10.0\",\n)\n" + } + }, + "crates_io__tinyvec_macros-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tinyvec_macros/0.1.1/download" + ], + "strip_prefix": "tinyvec_macros-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tinyvec_macros\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tinyvec_macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n" + } + }, + "crates_io__tokio-1.50.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio/1.50.0/download" + ], + "strip_prefix": "tokio-1.50.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__mio-1.1.1//:mio\",\n \"@crates_io__parking_lot-0.12.5//:parking_lot\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-apple-darwin\n \"@crates_io__signal-hook-registry-1.4.8//:signal_hook_registry\", # aarch64-apple-darwin\n \"@crates_io__socket2-0.6.3//:socket2\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # aarch64-unknown-linux-gnu\n \"@crates_io__signal-hook-registry-1.4.8//:signal_hook_registry\", # aarch64-unknown-linux-gnu\n \"@crates_io__socket2-0.6.3//:socket2\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__socket2-0.6.3//:socket2\", # x86_64-pc-windows-msvc\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu\n \"@crates_io__signal-hook-registry-1.4.8//:signal_hook_registry\", # x86_64-unknown-linux-gnu\n \"@crates_io__socket2-0.6.3//:socket2\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__libc-0.2.183//:libc\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__signal-hook-registry-1.4.8//:signal_hook_registry\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"@crates_io__socket2-0.6.3//:socket2\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n proc_macro_deps = [\n \"@crates_io__tokio-macros-2.6.1//:tokio_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"bytes\",\n \"default\",\n \"fs\",\n \"full\",\n \"io-std\",\n \"io-util\",\n \"libc\",\n \"macros\",\n \"mio\",\n \"net\",\n \"parking_lot\",\n \"process\",\n \"rt\",\n \"rt-multi-thread\",\n \"signal\",\n \"signal-hook-registry\",\n \"socket2\",\n \"sync\",\n \"time\",\n \"tokio-macros\",\n \"tracing\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"windows-sys\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.50.0\",\n)\n" + } + }, + "crates_io__tokio-macros-2.6.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-macros/2.6.1/download" + ], + "strip_prefix": "tokio-macros-2.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"tokio_macros\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.6.1\",\n)\n" + } + }, + "crates_io__tokio-rustls-0.26.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-rustls/0.26.4/download" + ], + "strip_prefix": "tokio-rustls-0.26.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_rustls\",\n deps = [\n \"@crates_io__rustls-0.23.37//:rustls\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"aws-lc-rs\",\n \"aws_lc_rs\",\n \"ring\",\n \"tls12\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-rustls\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.4\",\n)\n" + } + }, + "crates_io__tokio-stream-0.1.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-stream/0.1.18/download" + ], + "strip_prefix": "tokio-stream-0.1.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_stream\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"fs\",\n \"net\",\n \"time\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-stream\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.18\",\n)\n" + } + }, + "crates_io__tokio-tungstenite-0.21.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-tungstenite/0.21.0/download" + ], + "strip_prefix": "tokio-tungstenite-0.21.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_tungstenite\",\n deps = [\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tungstenite-0.21.0//:tungstenite\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"connect\",\n \"default\",\n \"handshake\",\n \"stream\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-tungstenite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.21.0\",\n)\n" + } + }, + "crates_io__tokio-tungstenite-0.28.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-tungstenite/0.28.0/download" + ], + "strip_prefix": "tokio-tungstenite-0.28.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_tungstenite\",\n deps = [\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tungstenite-0.28.0//:tungstenite\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"connect\",\n \"default\",\n \"handshake\",\n \"stream\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-tungstenite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.28.0\",\n)\n" + } + }, + "crates_io__tokio-util-0.7.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tokio-util/0.7.18/download" + ], + "strip_prefix": "tokio-util-0.7.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tokio_util\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-sink-0.3.32//:futures_sink\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"codec\",\n \"default\",\n \"io\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tokio-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.18\",\n)\n" + } + }, + "crates_io__toml-0.9.12-spec-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml/0.9.12+spec-1.1.0/download" + ], + "strip_prefix": "toml-0.9.12+spec-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml\",\n deps = [\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n \"@crates_io__serde_spanned-1.0.4//:serde_spanned\",\n \"@crates_io__toml_datetime-0.7.5-spec-1.1.0//:toml_datetime\",\n \"@crates_io__toml_parser-1.0.9-spec-1.1.0//:toml_parser\",\n \"@crates_io__toml_writer-1.0.6-spec-1.1.0//:toml_writer\",\n \"@crates_io__winnow-0.7.15//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"display\",\n \"parse\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.12+spec-1.1.0\",\n)\n" + } + }, + "crates_io__toml_datetime-0.6.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_datetime/0.6.11/download" + ], + "strip_prefix": "toml_datetime-0.6.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_datetime\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_datetime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.11\",\n)\n" + } + }, + "crates_io__toml_datetime-0.7.5-spec-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_datetime/0.7.5+spec-1.1.0/download" + ], + "strip_prefix": "toml_datetime-0.7.5+spec-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_datetime\",\n deps = [\n \"@crates_io__serde_core-1.0.228//:serde_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"serde\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_datetime\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.5+spec-1.1.0\",\n)\n" + } + }, + "crates_io__toml_edit-0.19.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_edit/0.19.15/download" + ], + "strip_prefix": "toml_edit-0.19.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_edit\",\n deps = [\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__toml_datetime-0.6.11//:toml_datetime\",\n \"@crates_io__winnow-0.5.40//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_edit\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.19.15\",\n)\n" + } + }, + "crates_io__toml_parser-1.0.9-spec-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_parser/1.0.9+spec-1.1.0/download" + ], + "strip_prefix": "toml_parser-1.0.9+spec-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_parser\",\n deps = [\n \"@crates_io__winnow-0.7.15//:winnow\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_parser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.9+spec-1.1.0\",\n)\n" + } + }, + "crates_io__toml_writer-1.0.6-spec-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/toml_writer/1.0.6+spec-1.1.0/download" + ], + "strip_prefix": "toml_writer-1.0.6+spec-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"toml_writer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=toml_writer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6+spec-1.1.0\",\n)\n" + } + }, + "crates_io__tonic-0.12.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tonic/0.12.3/download" + ], + "strip_prefix": "tonic-0.12.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tonic\",\n deps = [\n \"@crates_io__async-stream-0.3.6//:async_stream\",\n \"@crates_io__axum-0.7.9//:axum\",\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__h2-0.4.13//:h2\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__http-body-util-0.1.3//:http_body_util\",\n \"@crates_io__hyper-1.8.1//:hyper\",\n \"@crates_io__hyper-timeout-0.5.2//:hyper_timeout\",\n \"@crates_io__hyper-util-0.1.20//:hyper_util\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__pin-project-1.1.11//:pin_project\",\n \"@crates_io__prost-0.13.5//:prost\",\n \"@crates_io__socket2-0.5.10//:socket2\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-stream-0.1.18//:tokio_stream\",\n \"@crates_io__tower-0.4.13//:tower\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n proc_macro_deps = [\n \"@crates_io__async-trait-0.1.89//:async_trait\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"channel\",\n \"codegen\",\n \"default\",\n \"prost\",\n \"router\",\n \"server\",\n \"transport\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tonic\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.12.3\",\n)\n" + } + }, + "crates_io__tower-0.4.13": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower/0.4.13/download" + ], + "strip_prefix": "tower-0.4.13", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__indexmap-1.9.3//:indexmap\",\n \"@crates_io__pin-project-1.1.11//:pin_project\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__rand-0.8.5//:rand\",\n \"@crates_io__slab-0.4.12//:slab\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"__common\",\n \"balance\",\n \"buffer\",\n \"discover\",\n \"futures-core\",\n \"futures-util\",\n \"indexmap\",\n \"limit\",\n \"load\",\n \"make\",\n \"pin-project\",\n \"pin-project-lite\",\n \"rand\",\n \"ready-cache\",\n \"slab\",\n \"tokio\",\n \"tokio-util\",\n \"tracing\",\n \"util\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.13\",\n)\n" + } + }, + "crates_io__tower-0.5.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower/0.5.3/download" + ], + "strip_prefix": "tower-0.5.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower\",\n deps = [\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__sync_wrapper-1.0.2//:sync_wrapper\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"futures-core\",\n \"futures-util\",\n \"limit\",\n \"pin-project-lite\",\n \"sync_wrapper\",\n \"tokio\",\n \"tokio-util\",\n \"tracing\",\n \"util\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"retry\", # aarch64-apple-darwin\n \"timeout\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"retry\", # aarch64-unknown-linux-gnu\n \"timeout\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"retry\", # x86_64-pc-windows-msvc\n \"timeout\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"retry\", # x86_64-unknown-linux-gnu\n \"timeout\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"retry\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n \"timeout\", # x86_64-unknown-linux-gnu, x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.3\",\n)\n" + } + }, + "crates_io__tower-http-0.6.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-http/0.6.8/download" + ], + "strip_prefix": "tower-http-0.6.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_http\",\n deps = [\n \"@crates_io__async-compression-0.4.41//:async_compression\",\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-core-0.3.32//:futures_core\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__http-body-1.0.1//:http_body\",\n \"@crates_io__http-body-util-0.1.3//:http_body_util\",\n \"@crates_io__iri-string-0.7.10//:iri_string\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tower-0.5.3//:tower\",\n \"@crates_io__tower-layer-0.3.3//:tower_layer\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"async-compression\",\n \"decompression-br\",\n \"decompression-deflate\",\n \"decompression-gzip\",\n \"decompression-zstd\",\n \"follow-redirect\",\n \"futures-core\",\n \"futures-util\",\n \"iri-string\",\n \"tokio\",\n \"tokio-util\",\n \"tower\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-http\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.8\",\n)\n" + } + }, + "crates_io__tower-layer-0.3.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-layer/0.3.3/download" + ], + "strip_prefix": "tower-layer-0.3.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_layer\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-layer\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.3\",\n)\n" + } + }, + "crates_io__tower-service-0.3.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tower-service/0.3.3/download" + ], + "strip_prefix": "tower-service-0.3.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tower_service\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tower-service\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.3\",\n)\n" + } + }, + "crates_io__tracing-0.1.44": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing/0.1.44/download" + ], + "strip_prefix": "tracing-0.1.44", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tracing\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__pin-project-lite-0.2.17//:pin_project_lite\",\n \"@crates_io__tracing-core-0.1.36//:tracing_core\",\n ],\n proc_macro_deps = [\n \"@crates_io__tracing-attributes-0.1.31//:tracing_attributes\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"attributes\",\n \"default\",\n \"log\",\n \"std\",\n \"tracing-attributes\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.44\",\n)\n" + } + }, + "crates_io__tracing-attributes-0.1.31": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing-attributes/0.1.31/download" + ], + "strip_prefix": "tracing-attributes-0.1.31", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"tracing_attributes\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing-attributes\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.31\",\n)\n" + } + }, + "crates_io__tracing-core-0.1.36": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing-core/0.1.36/download" + ], + "strip_prefix": "tracing-core-0.1.36", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tracing_core\",\n deps = [\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"once_cell\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.36\",\n)\n" + } + }, + "crates_io__tracing-subscriber-0.3.23": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tracing-subscriber/0.3.23/download" + ], + "strip_prefix": "tracing-subscriber-0.3.23", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tracing_subscriber\",\n deps = [\n \"@crates_io__matchers-0.2.0//:matchers\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__regex-automata-0.4.14//:regex_automata\",\n \"@crates_io__sharded-slab-0.1.7//:sharded_slab\",\n \"@crates_io__thread_local-1.1.9//:thread_local\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n \"@crates_io__tracing-core-0.1.36//:tracing_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"env-filter\",\n \"fmt\",\n \"matchers\",\n \"once_cell\",\n \"registry\",\n \"sharded-slab\",\n \"std\",\n \"thread_local\",\n \"tracing\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tracing-subscriber\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.23\",\n)\n" + } + }, + "crates_io__try-lock-0.2.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/try-lock/0.2.5/download" + ], + "strip_prefix": "try-lock-0.2.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"try_lock\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=try-lock\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.5\",\n)\n" + } + }, + "crates_io__tungstenite-0.21.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tungstenite/0.21.0/download" + ], + "strip_prefix": "tungstenite-0.21.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tungstenite\",\n deps = [\n \"@crates_io__byteorder-1.5.0//:byteorder\",\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__data-encoding-2.10.0//:data_encoding\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__httparse-1.10.1//:httparse\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__rand-0.8.5//:rand\",\n \"@crates_io__sha1-0.10.6//:sha1\",\n \"@crates_io__thiserror-1.0.69//:thiserror\",\n \"@crates_io__url-2.5.8//:url\",\n \"@crates_io__utf-8-0.7.6//:utf8\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"data-encoding\",\n \"handshake\",\n \"http\",\n \"httparse\",\n \"sha1\",\n \"url\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tungstenite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.21.0\",\n)\n" + } + }, + "crates_io__tungstenite-0.28.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/tungstenite/0.28.0/download" + ], + "strip_prefix": "tungstenite-0.28.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"tungstenite\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__data-encoding-2.10.0//:data_encoding\",\n \"@crates_io__http-1.4.0//:http\",\n \"@crates_io__httparse-1.10.1//:httparse\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__rand-0.9.2//:rand\",\n \"@crates_io__sha1-0.10.6//:sha1\",\n \"@crates_io__thiserror-2.0.18//:thiserror\",\n \"@crates_io__utf-8-0.7.6//:utf8\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"data-encoding\",\n \"handshake\",\n \"http\",\n \"httparse\",\n \"sha1\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=tungstenite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.28.0\",\n)\n" + } + }, + "crates_io__typed-arena-1.7.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/typed-arena/1.7.0/download" + ], + "strip_prefix": "typed-arena-1.7.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"typed_arena\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typed-arena\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.7.0\",\n)\n" + } + }, + "crates_io__typenum-1.19.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/typenum/1.19.0/download" + ], + "strip_prefix": "typenum-1.19.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"typenum\",\n deps = [\n \"@crates_io__typenum-1.19.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typenum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.19.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"typenum\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=typenum\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.19.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__ua_generator-0.5.52": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3eebe5bf263176003918e31d3789d37dc848f142b72da8eb9cbd8b0e067287e9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ua_generator/0.5.52/download" + ], + "strip_prefix": "ua_generator-0.5.52", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ua_generator\",\n deps = [\n \"@crates_io__fastrand-2.3.0//:fastrand\",\n \"@crates_io__ua_generator-0.5.52//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"fetch\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ua_generator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.52\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"fetch\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__dotenvy-0.15.7//:dotenvy\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__toml-0.9.12-spec-1.1.0//:toml\",\n \"@crates_io__ureq-2.12.1//:ureq\",\n ],\n edition = \"2021\",\n pkg_name = \"ua_generator\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ua_generator\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.5.52\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__unicase-2.9.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicase/2.9.0/download" + ], + "strip_prefix": "unicase-2.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicase\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicase\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.9.0\",\n)\n" + } + }, + "crates_io__unicode-bidi-0.3.18": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-bidi/0.3.18/download" + ], + "strip_prefix": "unicode-bidi-0.3.18", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_bidi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-bidi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.18\",\n)\n" + } + }, + "crates_io__unicode-ident-1.0.24": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-ident/1.0.24/download" + ], + "strip_prefix": "unicode-ident-1.0.24", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_ident\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-ident\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.24\",\n)\n" + } + }, + "crates_io__unicode-normalization-0.1.25": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-normalization/0.1.25/download" + ], + "strip_prefix": "unicode-normalization-0.1.25", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_normalization\",\n deps = [\n \"@crates_io__tinyvec-1.10.0//:tinyvec\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-normalization\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.25\",\n)\n" + } + }, + "crates_io__unicode-properties-0.1.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-properties/0.1.4/download" + ], + "strip_prefix": "unicode-properties-0.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_properties\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-properties\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.4\",\n)\n" + } + }, + "crates_io__unicode-segmentation-1.12.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-segmentation/1.12.0/download" + ], + "strip_prefix": "unicode-segmentation-1.12.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_segmentation\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-segmentation\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.12.0\",\n)\n" + } + }, + "crates_io__unicode-width-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-width/0.2.2/download" + ], + "strip_prefix": "unicode-width-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_width\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cjk\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-width\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__unicode-xid-0.2.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-xid/0.2.6/download" + ], + "strip_prefix": "unicode-xid-0.2.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_xid\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-xid\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.6\",\n)\n" + } + }, + "crates_io__untrusted-0.9.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/untrusted/0.9.0/download" + ], + "strip_prefix": "untrusted-0.9.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"untrusted\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=untrusted\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.0\",\n)\n" + } + }, + "crates_io__ureq-2.12.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ureq/2.12.1/download" + ], + "strip_prefix": "ureq-2.12.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ureq\",\n deps = [\n \"@crates_io__base64-0.22.1//:base64\",\n \"@crates_io__brotli-decompressor-4.0.3//:brotli_decompressor\",\n \"@crates_io__encoding_rs-0.8.35//:encoding_rs\",\n \"@crates_io__flate2-1.1.9//:flate2\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__rustls-0.23.37//:rustls\",\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__url-2.5.8//:url\",\n \"@crates_io__webpki-roots-0.26.11//:webpki_roots\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"brotli\",\n \"charset\",\n \"default\",\n \"gzip\",\n \"json\",\n \"tls\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ureq\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.12.1\",\n)\n" + } + }, + "crates_io__url-2.5.8": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/url/2.5.8/download" + ], + "strip_prefix": "url-2.5.8", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"url\",\n deps = [\n \"@crates_io__form_urlencoded-1.2.2//:form_urlencoded\",\n \"@crates_io__idna-1.1.0//:idna\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=url\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.5.8\",\n)\n" + } + }, + "crates_io__utf-8-0.7.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/utf-8/0.7.6/download" + ], + "strip_prefix": "utf-8-0.7.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"utf8\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=utf-8\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.6\",\n)\n" + } + }, + "crates_io__utf8_iter-1.0.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/utf8_iter/1.0.4/download" + ], + "strip_prefix": "utf8_iter-1.0.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"utf8_iter\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=utf8_iter\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.4\",\n)\n" + } + }, + "crates_io__utf8parse-0.2.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/utf8parse/0.2.2/download" + ], + "strip_prefix": "utf8parse-0.2.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"utf8parse\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=utf8parse\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.2\",\n)\n" + } + }, + "crates_io__valuable-0.1.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/valuable/0.1.1/download" + ], + "strip_prefix": "valuable-0.1.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"valuable\",\n deps = [\n \"@crates_io__valuable-0.1.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=valuable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"valuable\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=valuable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.1.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__vcpkg-0.2.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/vcpkg/0.2.15/download" + ], + "strip_prefix": "vcpkg-0.2.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"vcpkg\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=vcpkg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.15\",\n)\n" + } + }, + "crates_io__version_check-0.9.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/version_check/0.9.5/download" + ], + "strip_prefix": "version_check-0.9.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"version_check\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=version_check\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.9.5\",\n)\n" + } + }, + "crates_io__walkdir-2.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/walkdir/2.5.0/download" + ], + "strip_prefix": "walkdir-2.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"walkdir\",\n deps = [\n \"@crates_io__same-file-1.0.6//:same_file\",\n ] + select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__winapi-util-0.1.11//:winapi_util\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=walkdir\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.5.0\",\n)\n" + } + }, + "crates_io__want-0.3.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/want/0.3.1/download" + ], + "strip_prefix": "want-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"want\",\n deps = [\n \"@crates_io__try-lock-0.2.5//:try_lock\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=want\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "crates_io__warp-0.3.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/warp/0.3.7/download" + ], + "strip_prefix": "warp-0.3.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"warp\",\n deps = [\n \"@crates_io__bytes-1.11.1//:bytes\",\n \"@crates_io__futures-channel-0.3.32//:futures_channel\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__headers-0.3.9//:headers\",\n \"@crates_io__http-0.2.12//:http\",\n \"@crates_io__hyper-0.14.32//:hyper\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__mime-0.3.17//:mime\",\n \"@crates_io__mime_guess-2.0.5//:mime_guess\",\n \"@crates_io__multer-2.1.0//:multer\",\n \"@crates_io__percent-encoding-2.3.2//:percent_encoding\",\n \"@crates_io__pin-project-1.1.11//:pin_project\",\n \"@crates_io__scoped-tls-1.0.1//:scoped_tls\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__serde_urlencoded-0.7.1//:serde_urlencoded\",\n \"@crates_io__tokio-1.50.0//:tokio\",\n \"@crates_io__tokio-tungstenite-0.21.0//:tokio_tungstenite\",\n \"@crates_io__tokio-util-0.7.18//:tokio_util\",\n \"@crates_io__tower-service-0.3.3//:tower_service\",\n \"@crates_io__tracing-0.1.44//:tracing\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"multer\",\n \"multipart\",\n \"tokio-tungstenite\",\n \"websocket\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=warp\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.7\",\n)\n" + } + }, + "crates_io__wasi-0.11.1-wasi-snapshot-preview1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download" + ], + "strip_prefix": "wasi-0.11.1+wasi-snapshot-preview1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasi\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.1+wasi-snapshot-preview1\",\n)\n" + } + }, + "crates_io__wasip2-1.0.2-wasi-0.2.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasip2/1.0.2+wasi-0.2.9/download" + ], + "strip_prefix": "wasip2-1.0.2+wasi-0.2.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasip2\",\n deps = [\n \"@crates_io__wit-bindgen-0.51.0//:wit_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasip2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.2+wasi-0.2.9\",\n)\n" + } + }, + "crates_io__wasip3-0.4.0-wasi-0.3.0-rc-2026-01-06": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasip3/0.4.0+wasi-0.3.0-rc-2026-01-06/download" + ], + "strip_prefix": "wasip3-0.4.0+wasi-0.3.0-rc-2026-01-06", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasip3\",\n deps = [\n \"@crates_io__wit-bindgen-0.51.0//:wit_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasip3\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0+wasi-0.3.0-rc-2026-01-06\",\n)\n" + } + }, + "crates_io__wasite-0.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasite/0.1.0/download" + ], + "strip_prefix": "wasite-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasite\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasite\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, + "crates_io__wasm-bindgen-0.2.114": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen/0.2.114/download" + ], + "strip_prefix": "wasm-bindgen-0.2.114", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__wasm-bindgen-0.2.114//:build_script_build\",\n \"@crates_io__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n ],\n proc_macro_deps = [\n \"@crates_io__wasm-bindgen-macro-0.2.114//:wasm_bindgen_macro\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.114\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n aliases = {\n \"@crates_io__rustversion-1.0.22//:rustversion\": \"rustversion_compat\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n ],\n edition = \"2021\",\n pkg_name = \"wasm-bindgen\",\n proc_macro_deps = [\n \"@crates_io__rustversion-1.0.22//:rustversion\",\n ],\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.114\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__wasm-bindgen-futures-0.4.64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-futures/0.4.64/download" + ], + "strip_prefix": "wasm-bindgen-futures-0.4.64", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_futures\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__js-sys-0.3.91//:js_sys\",\n \"@crates_io__once_cell-1.21.4//:once_cell\",\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"futures-util\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-futures\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.64\",\n)\n" + } + }, + "crates_io__wasm-bindgen-macro-0.2.114": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-macro/0.2.114/download" + ], + "strip_prefix": "wasm-bindgen-macro-0.2.114", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"wasm_bindgen_macro\",\n deps = [\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__wasm-bindgen-macro-support-0.2.114//:wasm_bindgen_macro_support\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.114\",\n)\n" + } + }, + "crates_io__wasm-bindgen-macro-support-0.2.114": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-macro-support/0.2.114/download" + ], + "strip_prefix": "wasm-bindgen-macro-support-0.2.114", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_macro_support\",\n deps = [\n \"@crates_io__bumpalo-3.20.2//:bumpalo\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__wasm-bindgen-shared-0.2.114//:wasm_bindgen_shared\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-macro-support\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.114\",\n)\n" + } + }, + "crates_io__wasm-bindgen-shared-0.2.114": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-bindgen-shared/0.2.114/download" + ], + "strip_prefix": "wasm-bindgen-shared-0.2.114", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_bindgen_shared\",\n deps = [\n \"@crates_io__unicode-ident-1.0.24//:unicode_ident\",\n \"@crates_io__wasm-bindgen-shared-0.2.114//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.114\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n links = \"wasm_bindgen\",\n pkg_name = \"wasm-bindgen-shared\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-bindgen-shared\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.114\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__wasm-encoder-0.244.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-encoder/0.244.0/download" + ], + "strip_prefix": "wasm-encoder-0.244.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_encoder\",\n deps = [\n \"@crates_io__leb128fmt-0.1.0//:leb128fmt\",\n \"@crates_io__wasmparser-0.244.0//:wasmparser\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"component-model\",\n \"std\",\n \"wasmparser\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-encoder\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.244.0\",\n)\n" + } + }, + "crates_io__wasm-metadata-0.244.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-metadata/0.244.0/download" + ], + "strip_prefix": "wasm-metadata-0.244.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_metadata\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__wasm-encoder-0.244.0//:wasm_encoder\",\n \"@crates_io__wasmparser-0.244.0//:wasmparser\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-metadata\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.244.0\",\n)\n" + } + }, + "crates_io__wasm-streams-0.5.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasm-streams/0.5.0/download" + ], + "strip_prefix": "wasm-streams-0.5.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasm_streams\",\n deps = [\n \"@crates_io__futures-util-0.3.32//:futures_util\",\n \"@crates_io__js-sys-0.3.91//:js_sys\",\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\",\n \"@crates_io__wasm-bindgen-futures-0.4.64//:wasm_bindgen_futures\",\n \"@crates_io__web-sys-0.3.91//:web_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasm-streams\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.0\",\n)\n" + } + }, + "crates_io__wasmparser-0.244.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wasmparser/0.244.0/download" + ], + "strip_prefix": "wasmparser-0.244.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wasmparser\",\n deps = [\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__hashbrown-0.15.5//:hashbrown\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__semver-1.0.27//:semver\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"component-model\",\n \"features\",\n \"hash-collections\",\n \"simd\",\n \"std\",\n \"validate\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wasmparser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.244.0\",\n)\n" + } + }, + "crates_io__web-sys-0.3.91": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/web-sys/0.3.91/download" + ], + "strip_prefix": "web-sys-0.3.91", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"web_sys\",\n deps = [\n \"@crates_io__js-sys-0.3.91//:js_sys\",\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"AbortController\",\n \"AbortSignal\",\n \"Blob\",\n \"BlobPropertyBag\",\n \"EventTarget\",\n \"File\",\n \"FormData\",\n \"Headers\",\n \"QueuingStrategy\",\n \"ReadableByteStreamController\",\n \"ReadableStream\",\n \"ReadableStreamByobReader\",\n \"ReadableStreamByobRequest\",\n \"ReadableStreamDefaultController\",\n \"ReadableStreamDefaultReader\",\n \"ReadableStreamGetReaderOptions\",\n \"ReadableStreamReadResult\",\n \"ReadableStreamReaderMode\",\n \"ReadableStreamType\",\n \"ReadableWritablePair\",\n \"Request\",\n \"RequestCache\",\n \"RequestCredentials\",\n \"RequestInit\",\n \"RequestMode\",\n \"Response\",\n \"ServiceWorkerGlobalScope\",\n \"StreamPipeOptions\",\n \"TransformStream\",\n \"TransformStreamDefaultController\",\n \"Transformer\",\n \"UnderlyingSink\",\n \"UnderlyingSource\",\n \"Window\",\n \"WorkerGlobalScope\",\n \"WritableStream\",\n \"WritableStreamDefaultController\",\n \"WritableStreamDefaultWriter\",\n \"default\",\n \"std\",\n ] + select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"CanvasRenderingContext2d\", # wasm32-unknown-unknown\n \"Document\", # wasm32-unknown-unknown\n \"DomRect\", # wasm32-unknown-unknown\n \"DomRectReadOnly\", # wasm32-unknown-unknown\n \"Element\", # wasm32-unknown-unknown\n \"HtmlCanvasElement\", # wasm32-unknown-unknown\n \"HtmlElement\", # wasm32-unknown-unknown\n \"Node\", # wasm32-unknown-unknown\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=web-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.91\",\n)\n" + } + }, + "crates_io__web-time-1.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/web-time/1.1.0/download" + ], + "strip_prefix": "web-time-1.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"web_time\",\n deps = select({\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__js-sys-0.3.91//:js_sys\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n \"@crates_io__wasm-bindgen-0.2.114//:wasm_bindgen\", # cfg(all(target_family = \"wasm\", target_os = \"unknown\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=web-time\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.0\",\n)\n" + } + }, + "crates_io__webpki-root-certs-1.0.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/webpki-root-certs/1.0.6/download" + ], + "strip_prefix": "webpki-root-certs-1.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"webpki_root_certs\",\n deps = [\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n ],\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=webpki-root-certs\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6\",\n)\n" + } + }, + "crates_io__webpki-roots-0.26.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/webpki-roots/0.26.11/download" + ], + "strip_prefix": "webpki-roots-0.26.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"webpki_roots\",\n deps = [\n \"@crates_io__webpki-roots-1.0.6//:webpki_roots\",\n ],\n aliases = {\n \"@crates_io__webpki-roots-1.0.6//:webpki_roots\": \"parent\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=webpki-roots\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.26.11\",\n)\n" + } + }, + "crates_io__webpki-roots-1.0.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/webpki-roots/1.0.6/download" + ], + "strip_prefix": "webpki-roots-1.0.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"webpki_roots\",\n deps = [\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\",\n ],\n aliases = {\n \"@crates_io__rustls-pki-types-1.14.0//:rustls_pki_types\": \"pki_types\",\n },\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=webpki-roots\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.6\",\n)\n" + } + }, + "crates_io__which-6.0.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/which/6.0.3/download" + ], + "strip_prefix": "which-6.0.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"which\",\n deps = [\n \"@crates_io__either-1.15.0//:either\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__home-0.5.12//:home\", # cfg(any(windows, unix, target_os = \"redox\"))\n \"@crates_io__rustix-0.38.44//:rustix\", # cfg(any(unix, target_os = \"wasi\", target_os = \"redox\"))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__home-0.5.12//:home\", # cfg(any(windows, unix, target_os = \"redox\"))\n \"@crates_io__rustix-0.38.44//:rustix\", # cfg(any(unix, target_os = \"wasi\", target_os = \"redox\"))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__rustix-0.38.44//:rustix\", # cfg(any(unix, target_os = \"wasi\", target_os = \"redox\"))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__home-0.5.12//:home\", # cfg(any(windows, unix, target_os = \"redox\"))\n \"@crates_io__winsafe-0.0.19//:winsafe\", # cfg(windows)\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__home-0.5.12//:home\", # cfg(any(windows, unix, target_os = \"redox\"))\n \"@crates_io__rustix-0.38.44//:rustix\", # cfg(any(unix, target_os = \"wasi\", target_os = \"redox\"))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__home-0.5.12//:home\", # cfg(any(windows, unix, target_os = \"redox\"))\n \"@crates_io__rustix-0.38.44//:rustix\", # cfg(any(unix, target_os = \"wasi\", target_os = \"redox\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=which\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"6.0.3\",\n)\n" + } + }, + "crates_io__whoami-1.6.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/whoami/1.6.1/download" + ], + "strip_prefix": "whoami-1.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"whoami\",\n deps = select({\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__wasite-0.1.0//:wasite\", # cfg(all(target_arch = \"wasm32\", target_os = \"wasi\"))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=whoami\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.6.1\",\n)\n" + } + }, + "crates_io__wide-0.7.33": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wide/0.7.33/download" + ], + "strip_prefix": "wide-0.7.33", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wide\",\n deps = [\n \"@crates_io__bytemuck-1.25.0//:bytemuck\",\n \"@crates_io__safe_arch-0.7.4//:safe_arch\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wide\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.33\",\n)\n" + } + }, + "crates_io__winapi-0.3.9": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi/0.3.9/download" + ], + "strip_prefix": "winapi-0.3.9", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi\",\n deps = [\n \"@crates_io__winapi-0.3.9//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cfg\",\n \"evntrace\",\n \"in6addr\",\n \"inaddr\",\n \"minwinbase\",\n \"ntsecapi\",\n \"sysinfoapi\",\n \"windef\",\n \"winioctl\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.9\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"cfg\",\n \"evntrace\",\n \"in6addr\",\n \"inaddr\",\n \"minwinbase\",\n \"ntsecapi\",\n \"sysinfoapi\",\n \"windef\",\n \"winioctl\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.9\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__winapi-i686-pc-windows-gnu-0.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-i686-pc-windows-gnu/0.4.0/download" + ], + "strip_prefix": "winapi-i686-pc-windows-gnu-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_i686_pc_windows_gnu\",\n deps = [\n \"@crates_io__winapi-i686-pc-windows-gnu-0.4.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-i686-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi-i686-pc-windows-gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-i686-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__winapi-util-0.1.11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-util/0.1.11/download" + ], + "strip_prefix": "winapi-util-0.1.11", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_util\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-sys-0.61.2//:windows_sys\", # cfg(windows)\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-util\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.11\",\n)\n" + } + }, + "crates_io__winapi-x86_64-pc-windows-gnu-0.4.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winapi-x86_64-pc-windows-gnu/0.4.0/download" + ], + "strip_prefix": "winapi-x86_64-pc-windows-gnu-0.4.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winapi_x86_64_pc_windows_gnu\",\n deps = [\n \"@crates_io__winapi-x86_64-pc-windows-gnu-0.4.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-x86_64-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2015\",\n pkg_name = \"winapi-x86_64-pc-windows-gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winapi-x86_64-pc-windows-gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.4.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows-0.62.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows/0.62.2/download" + ], + "strip_prefix": "windows-0.62.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows\",\n deps = [\n \"@crates_io__windows-collections-0.3.2//:windows_collections\",\n \"@crates_io__windows-core-0.62.2//:windows_core\",\n \"@crates_io__windows-future-0.3.2//:windows_future\",\n \"@crates_io__windows-numerics-0.3.1//:windows_numerics\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Wdk\",\n \"Wdk_System\",\n \"Wdk_System_SystemInformation\",\n \"Wdk_System_SystemServices\",\n \"Wdk_System_Threading\",\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Security\",\n \"Win32_Security_Authorization\",\n \"Win32_System\",\n \"Win32_System_Diagnostics\",\n \"Win32_System_Diagnostics_Debug\",\n \"Win32_System_Diagnostics_ToolHelp\",\n \"Win32_System_Kernel\",\n \"Win32_System_Memory\",\n \"Win32_System_Performance\",\n \"Win32_System_Power\",\n \"Win32_System_ProcessStatus\",\n \"Win32_System_Registry\",\n \"Win32_System_RemoteDesktop\",\n \"Win32_System_SystemInformation\",\n \"Win32_System_SystemServices\",\n \"Win32_System_Threading\",\n \"Win32_UI\",\n \"Win32_UI_Shell\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.62.2\",\n)\n" + } + }, + "crates_io__windows-collections-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-collections/0.3.2/download" + ], + "strip_prefix": "windows-collections-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_collections\",\n deps = [\n \"@crates_io__windows-core-0.62.2//:windows_core\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-collections\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__windows-core-0.62.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-core/0.62.2/download" + ], + "strip_prefix": "windows-core-0.62.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_core\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n \"@crates_io__windows-result-0.4.1//:windows_result\",\n \"@crates_io__windows-strings-0.5.1//:windows_strings\",\n ],\n proc_macro_deps = [\n \"@crates_io__windows-implement-0.60.2//:windows_implement\",\n \"@crates_io__windows-interface-0.59.3//:windows_interface\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.62.2\",\n)\n" + } + }, + "crates_io__windows-future-0.3.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-future/0.3.2/download" + ], + "strip_prefix": "windows-future-0.3.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_future\",\n deps = [\n \"@crates_io__windows-core-0.62.2//:windows_core\",\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n \"@crates_io__windows-threading-0.2.1//:windows_threading\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-future\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.2\",\n)\n" + } + }, + "crates_io__windows-implement-0.60.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-implement/0.60.2/download" + ], + "strip_prefix": "windows-implement-0.60.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"windows_implement\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-implement\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.60.2\",\n)\n" + } + }, + "crates_io__windows-interface-0.59.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-interface/0.59.3/download" + ], + "strip_prefix": "windows-interface-0.59.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"windows_interface\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-interface\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.59.3\",\n)\n" + } + }, + "crates_io__windows-link-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-link/0.2.1/download" + ], + "strip_prefix": "windows-link-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_link\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-link\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__windows-numerics-0.3.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-numerics/0.3.1/download" + ], + "strip_prefix": "windows-numerics-0.3.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_numerics\",\n deps = [\n \"@crates_io__windows-core-0.62.2//:windows_core\",\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-numerics\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.1\",\n)\n" + } + }, + "crates_io__windows-registry-0.6.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-registry/0.6.1/download" + ], + "strip_prefix": "windows-registry-0.6.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_registry\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n \"@crates_io__windows-result-0.4.1//:windows_result\",\n \"@crates_io__windows-strings-0.5.1//:windows_strings\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-registry\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.1\",\n)\n" + } + }, + "crates_io__windows-result-0.4.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-result/0.4.1/download" + ], + "strip_prefix": "windows-result-0.4.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_result\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-result\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.1\",\n)\n" + } + }, + "crates_io__windows-strings-0.5.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-strings/0.5.1/download" + ], + "strip_prefix": "windows-strings-0.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_strings\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-strings\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.1\",\n)\n" + } + }, + "crates_io__windows-sys-0.45.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.45.0/download" + ], + "strip_prefix": "windows-sys-0.45.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__windows-targets-0.42.2//:windows_targets\", # cfg(not(windows_raw_dylib))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.45.0\",\n)\n" + } + }, + "crates_io__windows-sys-0.48.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.48.0/download" + ], + "strip_prefix": "windows-sys-0.48.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@crates_io__windows-targets-0.48.5//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Security\",\n \"Win32_Storage\",\n \"Win32_Storage_FileSystem\",\n \"Win32_System\",\n \"Win32_System_Diagnostics\",\n \"Win32_System_Diagnostics_Debug\",\n \"Win32_System_Registry\",\n \"Win32_System_Time\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.0\",\n)\n" + } + }, + "crates_io__windows-sys-0.52.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.52.0/download" + ], + "strip_prefix": "windows-sys-0.52.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@crates_io__windows-targets-0.52.6//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Networking\",\n \"Win32_Networking_WinSock\",\n \"Win32_System\",\n \"Win32_System_IO\",\n \"Win32_System_Threading\",\n \"Win32_System_WindowsProgramming\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.0\",\n)\n" + } + }, + "crates_io__windows-sys-0.59.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.59.0/download" + ], + "strip_prefix": "windows-sys-0.59.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@crates_io__windows-targets-0.52.6//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.59.0\",\n)\n" + } + }, + "crates_io__windows-sys-0.60.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.60.2/download" + ], + "strip_prefix": "windows-sys-0.60.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@crates_io__windows-targets-0.53.5//:windows_targets\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.60.2\",\n)\n" + } + }, + "crates_io__windows-sys-0.61.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-sys/0.61.2/download" + ], + "strip_prefix": "windows-sys-0.61.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_sys\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"Wdk\",\n \"Wdk_Foundation\",\n \"Wdk_Storage\",\n \"Wdk_Storage_FileSystem\",\n \"Wdk_System\",\n \"Wdk_System_IO\",\n \"Win32\",\n \"Win32_Foundation\",\n \"Win32_Networking\",\n \"Win32_Networking_WinSock\",\n \"Win32_Security\",\n \"Win32_Security_Authentication\",\n \"Win32_Security_Authentication_Identity\",\n \"Win32_Security_Credentials\",\n \"Win32_Security_Cryptography\",\n \"Win32_Storage\",\n \"Win32_Storage_FileSystem\",\n \"Win32_System\",\n \"Win32_System_Com\",\n \"Win32_System_Console\",\n \"Win32_System_IO\",\n \"Win32_System_LibraryLoader\",\n \"Win32_System_Memory\",\n \"Win32_System_Pipes\",\n \"Win32_System_SystemInformation\",\n \"Win32_System_SystemServices\",\n \"Win32_System_Threading\",\n \"Win32_System_WindowsProgramming\",\n \"Win32_UI\",\n \"Win32_UI_Shell\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.61.2\",\n)\n" + } + }, + "crates_io__windows-targets-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.42.2/download" + ], + "strip_prefix": "windows-targets-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows_x86_64_msvc-0.42.2//:windows_x86_64_msvc\", # x86_64-pc-windows-msvc\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n" + } + }, + "crates_io__windows-targets-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.48.5/download" + ], + "strip_prefix": "windows-targets-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows_x86_64_msvc-0.48.5//:windows_x86_64_msvc\", # cfg(all(target_arch = \"x86_64\", target_env = \"msvc\", not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.48.5//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.48.5//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n" + } + }, + "crates_io__windows-targets-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.52.6/download" + ], + "strip_prefix": "windows-targets-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows_x86_64_msvc-0.52.6//:windows_x86_64_msvc\", # cfg(all(any(target_arch = \"x86_64\", target_arch = \"arm64ec\"), target_env = \"msvc\", not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.52.6//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.52.6//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n" + } + }, + "crates_io__windows-targets-0.53.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-targets/0.53.5/download" + ], + "strip_prefix": "windows-targets-0.53.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_targets\",\n deps = select({\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"@crates_io__windows_x86_64_msvc-0.53.1//:windows_x86_64_msvc\", # cfg(all(any(target_arch = \"x86_64\", target_arch = \"arm64ec\"), target_env = \"msvc\", not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.53.1//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"@crates_io__windows_x86_64_gnu-0.53.1//:windows_x86_64_gnu\", # cfg(all(target_arch = \"x86_64\", target_env = \"gnu\", not(target_abi = \"llvm\"), not(windows_raw_dylib)))\n ],\n \"//conditions:default\": [],\n }),\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-targets\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.5\",\n)\n" + } + }, + "crates_io__windows-threading-0.2.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows-threading/0.2.1/download" + ], + "strip_prefix": "windows-threading-0.2.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_threading\",\n deps = [\n \"@crates_io__windows-link-0.2.1//:windows_link\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows-threading\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.1\",\n)\n" + } + }, + "crates_io__windows_aarch64_gnullvm-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.42.2/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@crates_io__windows_aarch64_gnullvm-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_gnullvm-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.48.5/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@crates_io__windows_aarch64_gnullvm-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_gnullvm-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@crates_io__windows_aarch64_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_gnullvm-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_aarch64_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_gnullvm\",\n deps = [\n \"@crates_io__windows_aarch64_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_msvc-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.42.2/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@crates_io__windows_aarch64_msvc-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_msvc-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.48.5/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@crates_io__windows_aarch64_msvc-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_msvc-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.52.6/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@crates_io__windows_aarch64_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_aarch64_msvc-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_aarch64_msvc/0.53.1/download" + ], + "strip_prefix": "windows_aarch64_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_aarch64_msvc\",\n deps = [\n \"@crates_io__windows_aarch64_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_aarch64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_aarch64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnu-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.42.2/download" + ], + "strip_prefix": "windows_i686_gnu-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@crates_io__windows_i686_gnu-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnu-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.48.5/download" + ], + "strip_prefix": "windows_i686_gnu-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@crates_io__windows_i686_gnu-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnu-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.52.6/download" + ], + "strip_prefix": "windows_i686_gnu-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@crates_io__windows_i686_gnu-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnu-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnu/0.53.1/download" + ], + "strip_prefix": "windows_i686_gnu-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnu\",\n deps = [\n \"@crates_io__windows_i686_gnu-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnullvm-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_i686_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnullvm\",\n deps = [\n \"@crates_io__windows_i686_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_gnullvm-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_i686_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_gnullvm\",\n deps = [\n \"@crates_io__windows_i686_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_msvc-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.42.2/download" + ], + "strip_prefix": "windows_i686_msvc-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@crates_io__windows_i686_msvc-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_msvc-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.48.5/download" + ], + "strip_prefix": "windows_i686_msvc-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@crates_io__windows_i686_msvc-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_msvc-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.52.6/download" + ], + "strip_prefix": "windows_i686_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@crates_io__windows_i686_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_i686_msvc-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_i686_msvc/0.53.1/download" + ], + "strip_prefix": "windows_i686_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_i686_msvc\",\n deps = [\n \"@crates_io__windows_i686_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_i686_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_i686_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnu-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.42.2/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@crates_io__windows_x86_64_gnu-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnu-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.48.5/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@crates_io__windows_x86_64_gnu-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnu-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@crates_io__windows_x86_64_gnu-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnu-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnu/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_gnu-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnu\",\n deps = [\n \"@crates_io__windows_x86_64_gnu-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnu\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnu\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnullvm-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.42.2/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@crates_io__windows_x86_64_gnullvm-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnullvm-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.48.5/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@crates_io__windows_x86_64_gnullvm-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnullvm-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@crates_io__windows_x86_64_gnullvm-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_gnullvm-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_gnullvm/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_gnullvm-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_gnullvm\",\n deps = [\n \"@crates_io__windows_x86_64_gnullvm-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_gnullvm\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_gnullvm\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_msvc-0.42.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.42.2/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.42.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@crates_io__windows_x86_64_msvc-0.42.2//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.42.2\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.42.2\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_msvc-0.48.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.48.5/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.48.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@crates_io__windows_x86_64_msvc-0.48.5//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.48.5\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.48.5\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_msvc-0.52.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.52.6/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.52.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@crates_io__windows_x86_64_msvc-0.52.6//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.6\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.52.6\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__windows_x86_64_msvc-0.53.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/windows_x86_64_msvc/0.53.1/download" + ], + "strip_prefix": "windows_x86_64_msvc-0.53.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"windows_x86_64_msvc\",\n deps = [\n \"@crates_io__windows_x86_64_msvc-0.53.1//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.53.1\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"windows_x86_64_msvc\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=windows_x86_64_msvc\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.53.1\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__winnow-0.5.40": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winnow/0.5.40/download" + ], + "strip_prefix": "winnow-0.5.40", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winnow\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winnow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.5.40\",\n)\n" + } + }, + "crates_io__winnow-0.7.15": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winnow/0.7.15/download" + ], + "strip_prefix": "winnow-0.7.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winnow\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winnow\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.7.15\",\n)\n" + } + }, + "crates_io__winreg-0.52.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winreg/0.52.0/download" + ], + "strip_prefix": "winreg-0.52.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winreg\",\n deps = [\n \"@crates_io__cfg-if-1.0.4//:cfg_if\",\n \"@crates_io__windows-sys-0.48.0//:windows_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winreg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.52.0\",\n)\n" + } + }, + "crates_io__winsafe-0.0.19": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/winsafe/0.0.19/download" + ], + "strip_prefix": "winsafe-0.0.19", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"winsafe\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"kernel\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=winsafe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.0.19\",\n)\n" + } + }, + "crates_io__wit-bindgen-0.51.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-bindgen/0.51.0/download" + ], + "strip_prefix": "wit-bindgen-0.51.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_bindgen\",\n deps = [\n \"@crates_io__wit-bindgen-0.51.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.51.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2024\",\n pkg_name = \"wit-bindgen\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.51.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__wit-bindgen-core-0.51.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-bindgen-core/0.51.0/download" + ], + "strip_prefix": "wit-bindgen-core-0.51.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_bindgen_core\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__heck-0.5.0//:heck\",\n \"@crates_io__wit-parser-0.244.0//:wit_parser\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen-core\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.51.0\",\n)\n" + } + }, + "crates_io__wit-bindgen-rust-0.51.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-bindgen-rust/0.51.0/download" + ], + "strip_prefix": "wit-bindgen-rust-0.51.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_bindgen_rust\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__heck-0.5.0//:heck\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__prettyplease-0.2.37//:prettyplease\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__wasm-metadata-0.244.0//:wasm_metadata\",\n \"@crates_io__wit-bindgen-core-0.51.0//:wit_bindgen_core\",\n \"@crates_io__wit-bindgen-rust-0.51.0//:build_script_build\",\n \"@crates_io__wit-component-0.244.0//:wit_component\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen-rust\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.51.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__prettyplease-0.2.37//:prettyplease\",\n ],\n edition = \"2024\",\n pkg_name = \"wit-bindgen-rust\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen-rust\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.51.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__wit-bindgen-rust-macro-0.51.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-bindgen-rust-macro/0.51.0/download" + ], + "strip_prefix": "wit-bindgen-rust-macro-0.51.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"wit_bindgen_rust_macro\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__prettyplease-0.2.37//:prettyplease\",\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__wit-bindgen-core-0.51.0//:wit_bindgen_core\",\n \"@crates_io__wit-bindgen-rust-0.51.0//:wit_bindgen_rust\",\n \"@crates_io__wit-bindgen-rust-macro-0.51.0//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2024\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen-rust-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.51.0\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__prettyplease-0.2.37//:prettyplease\",\n ],\n edition = \"2024\",\n pkg_name = \"wit-bindgen-rust-macro\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-bindgen-rust-macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.51.0\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__wit-component-0.244.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-component/0.244.0/download" + ], + "strip_prefix": "wit-component-0.244.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_component\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__bitflags-2.11.0//:bitflags\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__wasm-encoder-0.244.0//:wasm_encoder\",\n \"@crates_io__wasm-metadata-0.244.0//:wasm_metadata\",\n \"@crates_io__wasmparser-0.244.0//:wasmparser\",\n \"@crates_io__wit-parser-0.244.0//:wit_parser\",\n ],\n proc_macro_deps = [\n \"@crates_io__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-component\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.244.0\",\n)\n" + } + }, + "crates_io__wit-parser-0.244.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/wit-parser/0.244.0/download" + ], + "strip_prefix": "wit-parser-0.244.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"wit_parser\",\n deps = [\n \"@crates_io__anyhow-1.0.102//:anyhow\",\n \"@crates_io__id-arena-2.3.0//:id_arena\",\n \"@crates_io__indexmap-2.13.0//:indexmap\",\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__semver-1.0.27//:semver\",\n \"@crates_io__serde-1.0.228//:serde\",\n \"@crates_io__serde_json-1.0.149//:serde_json\",\n \"@crates_io__unicode-xid-0.2.6//:unicode_xid\",\n \"@crates_io__wasmparser-0.244.0//:wasmparser\",\n ],\n proc_macro_deps = [\n \"@crates_io__serde_derive-1.0.228//:serde_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"decoding\",\n \"default\",\n \"serde\",\n \"serde_json\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=wit-parser\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.244.0\",\n)\n" + } + }, + "crates_io__writeable-0.6.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/writeable/0.6.2/download" + ], + "strip_prefix": "writeable-0.6.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"writeable\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=writeable\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.6.2\",\n)\n" + } + }, + "crates_io__xml5ever-0.18.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/xml5ever/0.18.1/download" + ], + "strip_prefix": "xml5ever-0.18.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"xml5ever\",\n deps = [\n \"@crates_io__log-0.4.29//:log\",\n \"@crates_io__mac-0.1.1//:mac\",\n \"@crates_io__markup5ever-0.12.1//:markup5ever\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=xml5ever\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.18.1\",\n)\n" + } + }, + "crates_io__yoke-0.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/yoke/0.8.1/download" + ], + "strip_prefix": "yoke-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"yoke\",\n deps = [\n \"@crates_io__stable_deref_trait-1.2.1//:stable_deref_trait\",\n \"@crates_io__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@crates_io__yoke-derive-0.8.1//:yoke_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"zerofrom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=yoke\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "crates_io__yoke-derive-0.8.1": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/yoke-derive/0.8.1/download" + ], + "strip_prefix": "yoke-derive-0.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"yoke_derive\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__synstructure-0.13.2//:synstructure\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=yoke-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.1\",\n)\n" + } + }, + "crates_io__zerocopy-0.8.42": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerocopy/0.8.42/download" + ], + "strip_prefix": "zerocopy-0.8.42", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerocopy\",\n deps = [\n \"@crates_io__zerocopy-0.8.42//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@crates_io__zerocopy-derive-0.8.42//:zerocopy_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"simd\",\n \"zerocopy-derive\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.42\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"simd\",\n \"zerocopy-derive\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"zerocopy\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.8.42\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__zerocopy-derive-0.8.42": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerocopy-derive/0.8.42/download" + ], + "strip_prefix": "zerocopy-derive-0.8.42", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerocopy_derive\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerocopy-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.42\",\n)\n" + } + }, + "crates_io__zerofrom-0.1.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerofrom/0.1.6/download" + ], + "strip_prefix": "zerofrom-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerofrom\",\n proc_macro_deps = [\n \"@crates_io__zerofrom-derive-0.1.6//:zerofrom_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerofrom\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "crates_io__zerofrom-derive-0.1.6": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerofrom-derive/0.1.6/download" + ], + "strip_prefix": "zerofrom-derive-0.1.6", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerofrom_derive\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n \"@crates_io__synstructure-0.13.2//:synstructure\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerofrom-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.6\",\n)\n" + } + }, + "crates_io__zeroize-1.8.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zeroize/1.8.2/download" + ], + "strip_prefix": "zeroize-1.8.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zeroize\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"default\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zeroize\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.8.2\",\n)\n" + } + }, + "crates_io__zerotrie-0.2.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerotrie/0.2.3/download" + ], + "strip_prefix": "zerotrie-0.2.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerotrie\",\n deps = [\n \"@crates_io__yoke-0.8.1//:yoke\",\n \"@crates_io__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@crates_io__displaydoc-0.2.5//:displaydoc\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"yoke\",\n \"zerofrom\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerotrie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.3\",\n)\n" + } + }, + "crates_io__zerovec-0.11.5": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerovec/0.11.5/download" + ], + "strip_prefix": "zerovec-0.11.5", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zerovec\",\n deps = [\n \"@crates_io__yoke-0.8.1//:yoke\",\n \"@crates_io__zerofrom-0.1.6//:zerofrom\",\n ],\n proc_macro_deps = [\n \"@crates_io__zerovec-derive-0.11.2//:zerovec_derive\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"derive\",\n \"yoke\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerovec\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.5\",\n)\n" + } + }, + "crates_io__zerovec-derive-0.11.2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zerovec-derive/0.11.2/download" + ], + "strip_prefix": "zerovec-derive-0.11.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"zerovec_derive\",\n deps = [\n \"@crates_io__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates_io__quote-1.0.45//:quote\",\n \"@crates_io__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zerovec-derive\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.11.2\",\n)\n" + } + }, + "crates_io__zmij-1.0.21": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zmij/1.0.21/download" + ], + "strip_prefix": "zmij-1.0.21", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zmij\",\n deps = [\n \"@crates_io__zmij-1.0.21//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zmij\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.21\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"zmij\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zmij\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.21\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__zstd-0.13.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zstd/0.13.3/download" + ], + "strip_prefix": "zstd-0.13.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zstd\",\n deps = [\n \"@crates_io__zstd-safe-7.2.4//:zstd_safe\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zstd\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.13.3\",\n)\n" + } + }, + "crates_io__zstd-safe-7.2.4": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zstd-safe/7.2.4/download" + ], + "strip_prefix": "zstd-safe-7.2.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zstd_safe\",\n deps = [\n \"@crates_io__zstd-safe-7.2.4//:build_script_build\",\n \"@crates_io__zstd-sys-2.0.16-zstd.1.5.7//:zstd_sys\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zstd-safe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"7.2.4\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n link_deps = [\n \"@crates_io__zstd-sys-2.0.16-zstd.1.5.7//:zstd_sys\",\n ],\n edition = \"2018\",\n pkg_name = \"zstd-safe\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zstd-safe\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"7.2.4\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates_io__zstd-sys-2.0.16-zstd.1.5.7": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/zstd-sys/2.0.16+zstd.1.5.7/download" + ], + "strip_prefix": "zstd-sys-2.0.16+zstd.1.5.7", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'spider'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"zstd_sys\",\n deps = [\n \"@crates_io__zstd-sys-2.0.16-zstd.1.5.7//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zstd-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.16+zstd.1.5.7\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates_io__cc-1.2.57//:cc\",\n \"@crates_io__pkg-config-0.3.32//:pkg_config\",\n ],\n edition = \"2018\",\n links = \"zstd\",\n pkg_name = \"zstd-sys\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=zstd-sys\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"2.0.16+zstd.1.5.7\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + } + } + } + } + }, + "facts": {} +} diff --git a/README.md b/README.md index 09271b5f3..85e63879d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,234 @@ # Spider -Web spider framework that can spider a domain and collect useful informations about the pages it visits. +[![Build Status](https://github.com/spider-rs/spider/actions/workflows/rust.yml/badge.svg)](https://github.com/spider-rs/spider/actions) +[![Crates.io](https://img.shields.io/crates/v/spider.svg)](https://crates.io/crates/spider) +[![Downloads](https://img.shields.io/crates/d/spider.svg)](https://crates.io/crates/spider) +[![Documentation](https://docs.rs/spider/badge.svg)](https://docs.rs/spider) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Discord](https://img.shields.io/discord/1254585814021832755.svg?logo=discord&style=flat-square)](https://discord.spider.cloud) -## Instalation +[Website](https://spider.cloud) | +[Guides](https://spider.cloud/guides) | +[API Docs](https://docs.rs/spider/latest/spider) | +[Examples](./examples/) | +[Discord](https://discord.spider.cloud) - $ cargo build --release +A high-performance web crawler and scraper for Rust. [200-1000x faster](#benchmarks) than popular alternatives, with HTTP, headless Chrome, and WebDriver rendering in a single library. -Run either with: +- **Crawl 100k+ pages in minutes** on a single machine. [See benchmarks.](#benchmarks) +- **HTTP, Chrome CDP, WebDriver, and [AI automation](./spider_agent/)** in one dependency. +- **Production-ready** with caching, proxy rotation, anti-bot bypass, and [distributed crawling](./spider_worker/). [Feature-gated](https://doc.rust-lang.org/cargo/reference/features.html) so you only compile what you use. -`cargo run` +## Quick Start -or directly with +### Command Line -`./target/debug/rust-crawler` +```bash +cargo install spider_cli +spider --url https://example.com +``` +### Rust + +```toml +[dependencies] +spider = "2" +``` + +```rust +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://example.com"); + website.crawl().await; + println!("Pages found: {}", website.get_links().len()); +} +``` + +### Streaming + +Process each page the moment it's crawled, not after: + +```rust +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://example.com"); + let mut rx = website.subscribe(0).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx.recv().await { + println!("- {}", page.get_url()); + } + }); + + website.crawl().await; + website.unsubscribe(); +} +``` + +### Headless Chrome + +Add one feature flag to render JavaScript-heavy pages: + +```toml +[dependencies] +spider = { version = "2", features = ["chrome"] } +``` + +```rust +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://example.com") + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .build() + .unwrap(); + + website.crawl().await; +} +``` + +> Also supports [WebDriver](./examples/webdriver.rs) (Selenium Grid, remote browsers) and [AI-driven automation](./spider_agent/). See [examples](./examples/) for more. + +## Benchmarks + +Crawling 185 pages ([source](./benches/BENCHMARKS.md), 10 samples averaged): + +**Apple M1 Max** (10-core, 64 GB RAM): + +| Crawler | Language | Time | vs Spider | +|---------|----------|-----:|----------:| +| **spider** | **Rust** | **73 ms** | **baseline** | +| node-crawler | JavaScript | 15 s | 205x slower | +| colly | Go | 32 s | 438x slower | +| wget | C | 70 s | 959x slower | + +**Linux** (2-core, 7 GB RAM): + +| Crawler | Language | Time | vs Spider | +|---------|----------|-----:|----------:| +| **spider** | **Rust** | **50 ms** | **baseline** | +| node-crawler | JavaScript | 3.4 s | 68x slower | +| colly | Go | 30 s | 600x slower | +| wget | C | 60 s | 1200x slower | + +The gap grows with site size. Spider handles 100k+ pages in minutes where other crawlers take hours. This comes from Rust's async runtime ([tokio](https://tokio.rs)), lock-free data structures, and optional [io_uring](https://en.wikipedia.org/wiki/Io_uring) on Linux. [Full details](./benches/BENCHMARKS.md) + +## Why Spider? + +Most crawlers force a choice between fast HTTP-only or slow-but-flexible browser automation. Spider supports both, and you can mix them in the same crawl. + +**Supports HTTP, Chrome, and WebDriver.** Switch rendering modes with a feature flag. Use HTTP for speed, Chrome CDP for JavaScript-heavy pages, and WebDriver for Selenium Grid or cross-browser testing. + +**Only compile what you use.** Every optional capability (Chrome, caching, proxies, AI) lives behind a [Cargo feature flag](https://doc.rust-lang.org/cargo/reference/features.html). A minimal `spider = "2"` stays lean. + +**Built for production.** Caching (memory, disk, hybrid), proxy rotation, anti-bot fingerprinting, ad blocking, depth budgets, cron scheduling, and distributed workers. All of this has been hardened through [Spider Cloud](https://spider.cloud). + +**AI automation included.** [spider_agent](./spider_agent/) adds multimodal LLM-driven automation: navigate pages, fill forms, solve challenges, and extract structured data with OpenAI or any compatible API. + +## Features + +
+Crawling + +- Concurrent and streaming crawls with backpressure +- [Decentralized crawling](./spider_worker/) for horizontal scaling +- Caching: memory, disk (SQLite), or [hybrid Chrome cache](./examples/cache_chrome_hybrid.rs) +- Proxy support with rotation +- Cron job scheduling +- Depth budgeting, blacklisting, whitelisting +- Smart mode that auto-detects JS-rendered content and upgrades to Chrome + +
+ +
+Browser Automation + +- [Chrome DevTools Protocol](https://github.com/spider-rs/chromey): headless or headed, stealth mode, screenshots, request interception +- [WebDriver](./examples/webdriver.rs): Selenium Grid, remote browsers, cross-browser testing +- AI-powered challenge solving (deterministic + [Chrome built-in AI](https://developer.chrome.com/docs/ai/prompt-api)) +- [Anti-bot fingerprinting](https://github.com/spider-rs/spider_fingerprint), [ad blocking](https://github.com/spider-rs/spider_network_blocker), [firewall](https://github.com/spider-rs/spider_firewall) + +
+ +
+Data Processing + +- [HTML transformations](https://github.com/spider-rs/spider_transformations) (Markdown, text, structured extraction) +- CSS/XPath scraping with [spider_utils](./spider_utils/README.md#CSS_Scraping) +- [OpenAI](./examples/openai.rs) and [Gemini](./examples/gemini.rs) integration for content analysis + +
+ +
+AI Agent + +- [spider_agent](./spider_agent/): concurrent-safe multimodal web automation agent +- Multiple LLM providers (OpenAI, any OpenAI-compatible API, Chrome built-in AI) +- Web research with search providers (Serper, Brave, Bing, Tavily) +- 110 built-in automation skills for web challenges + +
+ +## Spider Cloud + +For managed proxy rotation, anti-bot bypass, and CAPTCHA handling, [Spider Cloud](https://spider.cloud) plugs in with one line: + +```rust +let mut website = Website::new("https://protected-site.com") + .with_spider_cloud("your-api-key") // enable with features = ["spider_cloud"] + .build() + .unwrap(); +``` + +| Mode | Strategy | Best For | +|------|----------|----------| +| **Proxy** (default) | All traffic through Spider Cloud proxy | General crawling with IP rotation | +| **Smart** (recommended) | Proxy + auto-fallback on bot detection | Production (speed + reliability) | +| **Fallback** | Direct first, API on failure | Cost-efficient, most sites work without help | +| **Unblocker** | All requests through unblocker | Aggressive bot protection | + +> Free credits on signup. [Get started at spider.cloud](https://spider.cloud) + +## Get Spider + +| Package | Language | Install | +|---------|----------|---------| +| [spider](https://crates.io/crates/spider) | Rust | `cargo add spider` | +| [spider_cli](./spider_cli/) | CLI | `cargo install spider_cli` | +| [spider-nodejs](https://github.com/spider-rs/spider-nodejs) | Node.js | `npm i @spider-rs/spider-rs` | +| [spider-py](https://github.com/spider-rs/spider-py) | Python | `pip install spider_rs` | +| [spider_agent](./spider_agent/) | Rust | `cargo add spider --features agent` | + +### Cloud and Remote + +| Package | Description | +|---------|-------------| +| [Spider Cloud](https://spider.cloud) | Managed crawling infrastructure, no setup needed | +| [spider-clients](https://github.com/spider-rs/spider-clients) | SDKs for Spider Cloud in multiple languages | +| [spider-browser](https://github.com/spider-rs/spider-browser) | Remote access to Spider's Rust browser | + +## Resources + +- [64 examples](./examples/) covering crawling, Chrome, WebDriver, AI, caching, and more +- [API documentation](https://docs.rs/spider/latest/spider) +- [Benchmarks](./benches/BENCHMARKS.md) +- [Changelog](CHANGELOG.md) + +## Contributing + +Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines. + +Spider has been actively developed for the past 4 years. Join the [Discord](https://discord.spider.cloud) for questions and discussion. + +## License + +[MIT](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..64309c994 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in Spider, please report it responsibly. + +**Email:** [security@spider.cloud](mailto:security@spider.cloud) + +Please include: + +- Description of the vulnerability +- Steps to reproduce +- Affected versions +- Any potential impact + +We will acknowledge your report within 48 hours and aim to release a fix within 7 days for critical issues. + +## Scope + +This policy covers the `spider`, `spider_agent`, `spider_cli`, `spider_utils`, `spider_worker`, and related crates in this repository. + +## Supported Versions + +Security fixes are applied to the latest release only. We recommend always using the most recent version. diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 000000000..ad283f2be --- /dev/null +++ b/WORKSPACE.bzlmod @@ -0,0 +1,95 @@ +# Spider workspace - Bazel build configuration +# Migration from Cargo to Bazel for multi-language support (Rust + Go + Node.js) + +workspace(name = "spider") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +############################################################################### +# Rust toolchain setup +############################################################################### + +http_archive( + name = "rules_rust", + sha256 = "e494ee5af94068e3b8e5e9d70e31e8d66ff6b1d95f51c0c26468b71c82c5c2b3", + urls = ["https://github.com/bazelbuild/rules_rust/releases/download/0.56.0/rules_rust-0.56.0.tar.gz"], +) + +load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains") + +rules_rust_dependencies() + +rust_register_toolchains() + +############################################################################### +# crate_universe for Cargo dependencies +############################################################################### + +load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies") + +crate_universe_dependencies(bootstrapped = True) + +# Load the crates repository generated from Cargo.lock +load("//:crates.bzl", "crate_repositories") + +crate_repositories() + +############################################################################### +# Platform definitions +############################################################################### + +http_archive( + name = "platforms", + sha256 = "8150406605389ececb6da07cbcb509d5637c03a4f371be18d5dc7e7bc5c77572", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.10/platforms-0.0.10.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.10/platforms-0.0.10.tar.gz", + ], +) + +############################################################################### +# Skylib for common Bazel utilities +############################################################################### + +http_archive( + name = "bazel_skylib", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", + ], +) + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() + +############################################################################### +# Go toolchain setup (for multi-language benchmarks) +# Uncomment when Go support is needed +############################################################################### + +# http_archive( +# name = "io_bazel_rules_go", +# urls = ["https://github.com/bazelbuild/rules_go/releases/download/v0.50.1/rules_go-v0.50.1.zip"], +# ) +# +# load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") +# +# go_rules_dependencies() +# go_register_toolchains() + +############################################################################### +# External spider crates (published on crates.io) +############################################################################### + +# These are external spider ecosystem crates that are published +# They will be resolved via crate_universe from Cargo.lock + +# spider_fingerprint - fingerprinting utilities +# spider_firewall - firewall detection (optional) +# spider_scraper - HTML scraping +# spider_transformations - content transformations +# spider_skills - skill definitions for agents +# chromey - Chrome DevTools Protocol implementation +# llm_models_spider - LLM model capabilities \ No newline at end of file diff --git a/all_crates.txt b/all_crates.txt new file mode 100644 index 000000000..089c11ba8 --- /dev/null +++ b/all_crates.txt @@ -0,0 +1,127 @@ +@crates_io//:ahash +@crates_io//:ahash-0.8.12 +@crates_io//:aho-corasick +@crates_io//:aho-corasick-1.1.4 +@crates_io//:async-trait +@crates_io//:async-trait-0.1.89 +@crates_io//:auto_encoder +@crates_io//:auto_encoder-0.1.10 +@crates_io//:base64 +@crates_io//:base64-0.22.1 +@crates_io//:bytes +@crates_io//:bytes-1.11.1 +@crates_io//:case_insensitive_string +@crates_io//:case_insensitive_string-0.2.10 +@crates_io//:chromey +@crates_io//:chromey-2.40.0 +@crates_io//:clap +@crates_io//:clap-4.6.0 +@crates_io//:console-subscriber +@crates_io//:console-subscriber-0.4.1 +@crates_io//:convert_case +@crates_io//:convert_case-0.6.0 +@crates_io//:cookie +@crates_io//:cookie-0.18.1 +@crates_io//:criterion +@crates_io//:criterion-0.8.2 +@crates_io//:dashmap +@crates_io//:dashmap-6.1.0 +@crates_io//:dotenvy +@crates_io//:dotenvy-0.15.7 +@crates_io//:env_logger +@crates_io//:env_logger-0.11.9 +@crates_io//:fastrand +@crates_io//:fastrand-2.3.0 +@crates_io//:flexbuffers +@crates_io//:flexbuffers-25.12.19 +@crates_io//:futures +@crates_io//:futures-0.3.32 +@crates_io//:h2 +@crates_io//:h2-0.4.13 +@crates_io//:hashbrown +@crates_io//:hashbrown-0.16.1 +@crates_io//:home +@crates_io//:home-0.5.12 +@crates_io//:htr +@crates_io//:htr-0.5.27 +@crates_io//:io-uring +@crates_io//:io-uring-0.7.11 +@crates_io//:lazy_static +@crates_io//:lazy_static-1.5.0 +@crates_io//:libc +@crates_io//:libc-0.2.183 +@crates_io//:llm_models_spider +@crates_io//:llm_models_spider-0.1.42 +@crates_io//:log +@crates_io//:log-0.4.29 +@crates_io//:lol_html +@crates_io//:lol_html-2.7.2 +@crates_io//:num_cpus +@crates_io//:num_cpus-1.17.0 +@crates_io//:parking_lot +@crates_io//:parking_lot-0.12.5 +@crates_io//:percent-encoding +@crates_io//:percent-encoding-2.3.2 +@crates_io//:phf +@crates_io//:phf-0.11.3 +@crates_io//:pin-project-lite +@crates_io//:pin-project-lite-0.2.17 +@crates_io//:quick-xml +@crates_io//:quick-xml-0.39.2 +@crates_io//:rand +@crates_io//:rand-0.9.2 +@crates_io//:regex +@crates_io//:regex-1.12.3 +@crates_io//:reqwest +@crates_io//:reqwest-0.13.2 +@crates_io//:serde +@crates_io//:serde-1.0.228 +@crates_io//:serde_json +@crates_io//:serde_json-1.0.149 +@crates_io//:serde_regex +@crates_io//:serde_regex-1.1.0 +@crates_io//:smallvec +@crates_io//:smallvec-1.15.1 +@crates_io//:spider_fingerprint +@crates_io//:spider_fingerprint-2.38.1 +@crates_io//:spider_scraper +@crates_io//:spider_scraper-0.1.2 +@crates_io//:spider_transformations +@crates_io//:spider_transformations-2.37.115 +@crates_io//:spider_utils +@crates_io//:spider_utils-2.47.18 +@crates_io//:sqlx +@crates_io//:sqlx-0.8.6 +@crates_io//:srcs +@crates_io//:statrs +@crates_io//:statrs-0.18.0 +@crates_io//:string-interner +@crates_io//:string-interner-0.19.0 +@crates_io//:string_concat +@crates_io//:string_concat-0.0.1 +@crates_io//:strum +@crates_io//:strum-0.27.2 +@crates_io//:sxd-document +@crates_io//:sxd-document-0.3.2 +@crates_io//:sxd-xpath +@crates_io//:sxd-xpath-0.4.2 +@crates_io//:sysinfo +@crates_io//:sysinfo-0.38.4 +@crates_io//:thiserror +@crates_io//:thiserror-2.0.18 +@crates_io//:tokio +@crates_io//:tokio-1.50.0 +@crates_io//:tokio-stream +@crates_io//:tokio-stream-0.1.18 +@crates_io//:tower +@crates_io//:tower-0.5.3 +@crates_io//:ua_generator +@crates_io//:ua_generator-0.5.52 +@crates_io//:unicode-normalization +@crates_io//:unicode-normalization-0.1.25 +@crates_io//:url +@crates_io//:url-2.5.8 +@crates_io//:warp +@crates_io//:warp-0.3.7 +@crates_io//:which +@crates_io//:which-6.0.3 diff --git a/bazel-bin b/bazel-bin new file mode 120000 index 000000000..70a59d501 --- /dev/null +++ b/bazel-bin @@ -0,0 +1 @@ +/home/zhadainian/.cache/bazel/_bazel_zhadainian/384d3d3edb67d5c7af15a5ec5c47b929/execroot/_main/bazel-out/k8-fastbuild/bin \ No newline at end of file diff --git a/bazel-out b/bazel-out new file mode 120000 index 000000000..584b701b2 --- /dev/null +++ b/bazel-out @@ -0,0 +1 @@ +/home/zhadainian/.cache/bazel/_bazel_zhadainian/384d3d3edb67d5c7af15a5ec5c47b929/execroot/_main/bazel-out \ No newline at end of file diff --git a/bazel-spider b/bazel-spider new file mode 120000 index 000000000..49a9d728b --- /dev/null +++ b/bazel-spider @@ -0,0 +1 @@ +/home/zhadainian/.cache/bazel/_bazel_zhadainian/384d3d3edb67d5c7af15a5ec5c47b929/execroot/_main \ No newline at end of file diff --git a/bazel-testlogs b/bazel-testlogs new file mode 120000 index 000000000..381dd76b5 --- /dev/null +++ b/bazel-testlogs @@ -0,0 +1 @@ +/home/zhadainian/.cache/bazel/_bazel_zhadainian/384d3d3edb67d5c7af15a5ec5c47b929/execroot/_main/bazel-out/k8-fastbuild/testlogs \ No newline at end of file diff --git a/benches/.gitignore b/benches/.gitignore new file mode 100644 index 000000000..62aa81a43 --- /dev/null +++ b/benches/.gitignore @@ -0,0 +1,10 @@ +package.json +package-lock.json +node_modules +node-crawler.js +go-colly.go +go.mod +go.sum +output.txt +gospider +build \ No newline at end of file diff --git a/benches/BENCHMARKS.md b/benches/BENCHMARKS.md new file mode 100644 index 000000000..bda822bb8 --- /dev/null +++ b/benches/BENCHMARKS.md @@ -0,0 +1,69 @@ +# Benchmarks + +## Table of Contents + +- [Benchmark Results](#benchmark-results) + - [crawl-speed](#crawl-speed) + +## Benchmark Results + +All test are done remotely using 10-100 samples with one decentralized worker on the same host machine. We want to keep the benchmarks close to a real world scenario reason for network IO instead of spinning up a local website. + +### mac + +```sh +---------------------- +mac Apple M1 Max +10-core CPU +64 GB of RAM memory +1 TB of SSD disk space +----------------------- + +Test url: `https://rsseau.fr/` + +185 pages +``` + +| | `libraries` | +| :--------------------------------------- | :---------- | +| **`Rust[spider]: crawl 10 samples`** | `73ms` | +| **`Go[colly]: crawl 10 samples`** | `32s` | +| **`Node.js[crawler]: crawl 10 samples`** | `15s` | +| **`C[wget]: crawl 10 samples`** | `70s` | + +### linux + +```sh +---------------------- +linux ubuntu-latest +2-core CPU +7 GB of RAM memory +14 GB of SSD disk space +----------------------- + +Test url: `https://rsseau.fr/` + +185 pages +``` + +| | `libraries` | +| :--------------------------------------- | :---------- | +| **`Rust[spider]: crawl 10 samples`** | `50ms` | +| **`Go[colly]: crawl 10 samples`** | `30s` | +| **`Node.js[crawler]: crawl 10 samples`** | `3.4s` | +| **`C[wget]: crawl 10 samples`** | `60s` | + +The concurrent benchmarks are averaged across 10 individual runs for 10 concurrent crawls with 10 sample counts. + +In order for us to get better metrics we need to test the concurrency and simultaneous runs with a larger website. Favorably a website that can spin up inside the local container to avoid latency issues. The multi-threaded crawling capabilities shines brighter the larger the website. +Currently even with a small website this package still runs faster than the top OSS crawlers to date. [Spider](https://github.com/spider-rs/spider/tree/main/spider) is capable of crawling over 100k pages between 1-10 minutes depending on the website and OS. When spider is used decentralized it can handle IO within fractions of the time depending on the specs and amount of workers. The IO handling in linux performs drastically better than macOS and windows. + +_Note_: Nodejs concurrency heavily impacts each additional run. As soon as you add multiple crawlers with nodejs the performance reduces over 2x plus per, while other lanaguages that can handle concurrency scale effectively. + +## CI + +You need a dedicated machine to get non flakey results. [Github Actions](https://github.com/spider-rs/spider/actions) results may differ across runs due to the shared env and the crawler built to scale across workloads. + +## Env + +Adjusting the target url for the massive bench crawling can be done using the env variable `SPIDER_BENCH_URL_LARGE`. diff --git a/benches/BUILD.bazel b/benches/BUILD.bazel new file mode 100644 index 000000000..c30ee8e82 --- /dev/null +++ b/benches/BUILD.bazel @@ -0,0 +1,49 @@ +""" +Benchmarks - Performance benchmarks for Spider (Rust benchmarks). +Go and Node.js support available when configured. +""" + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +# Rust crawl benchmark +rust_binary( + name = "crawl_bench", + srcs = glob(["*.rs"]), + crate_root = "crawl.rs", + edition = "2021", + deps = [ + "//spider:spider", + "//spider_utils:spider_utils", + "@crates_io//:criterion", + "@crates_io//:tokio", + ], + rustc_flags = [ + "--cfg", + 'feature="async_tokio"', + "--cfg", + 'feature="html_reports"', + ], + testonly = True, +) + +# Rust core benchmark +rust_binary( + name = "core_bench", + srcs = ["core.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "//spider_utils:spider_utils", + "@crates_io//:criterion", + "@crates_io//:tokio", + ], + rustc_flags = [ + "--cfg", + 'feature="async_tokio"', + "--cfg", + 'feature="html_reports"', + ], + testonly = True, +) \ No newline at end of file diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 000000000..4e1622854 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "benches" +version = "0.0.0" +publish = false +edition = "2021" + +[dependencies] +criterion = { version = "0.8", features = ["html_reports", "async_tokio"] } +spider = { path = "../spider" } +spider_utils = { path = "../spider_utils" } + +[[bench]] +name = "crawl" +path = "crawl.rs" +harness = false + +[[bench]] +name = "core" +path = "core.rs" +harness = false + +[features] +decentralized = ["spider/decentralized"] diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 000000000..f50f13b64 --- /dev/null +++ b/benches/README.md @@ -0,0 +1,27 @@ +# benches + +[![Benches](https://github.com/madeindjs/spider/actions/workflows/bench.yml/badge.svg)](https://github.com/madeindjs/spider/actions/workflows/bench.yml) + +This folder consists of benches between different cases within the library and including comparison between other choices. + +## Initial benchmarks + +We have comparisons set against 4 different languages and libs that can be used to crawl a web page. + +### Crawl + +How fast can we crawl all pages on a medium sized website. Tests are ordered between the largest to smallest runtimes needed. All examples use the same html selector to gather the pages for a website. + +10 samples between each run on `https://spider.cloud`: + +1. [Node.js](./node_crawler.rs) - node-crawler +1. [Go](./go_colly.rs) - Colly +1. [Rust](./crawl.rs) - Spider +1. C - wget + +## Notes + +1. nodejs takes the cpu to 100% when crawling and performance suffers drastically when concurrent. +1. wget under performs when latency is being considered. + +You can view the latest [benches here](./BENCHMARKS.md) diff --git a/benches/build.rs b/benches/build.rs new file mode 100644 index 000000000..7520b3023 --- /dev/null +++ b/benches/build.rs @@ -0,0 +1,94 @@ +use std::process::Command; + +pub mod go_colly; +pub mod node_crawler; + +/// build executables for benchmarks +pub fn main() { + node_crawler::gen_crawl(); + go_colly::gen_crawl(); + + // install spider_worker + Command::new("cargo") + .args(["install", "spider_worker"]) + .output() + .expect("cargo install spider_worker failed"); + // install go deps + Command::new("go") + .args(["mod", "init", "example.com/gospider"]) + .output() + .expect("go init failed"); + Command::new("go") + .args(["get", "-u", "github.com/gocolly/colly/v2"]) + .output() + .expect("go get colly failed"); + Command::new("go") + .args(["mod", "tidy"]) + .output() + .expect("go tidy failed"); + Command::new("go") + .arg("build") + .output() + .expect("go build failed"); + // install node deps + Command::new("npm") + .args(["init", "-y"]) + .output() + .expect("npm init failed"); + Command::new("npm") + .args(["i", "crawler", "--save"]) + .output() + .expect("npm install crawler failed"); + + // Install wget based on the operating system + if cfg!(target_os = "linux") { + if is_debian_based() { + Command::new("apt-get") + .args(["install", "-y", "wget"]) + .output() + .expect("apt-get install wget failed"); + } else if is_rpm_based() { + Command::new("yum") + .args(["install", "-y", "wget"]) + .output() + .expect("yum install wget failed"); + } + } else if cfg!(target_os = "macos") { + Command::new("brew") + .args(["install", "wget"]) + .output() + .expect("brew install wget failed"); + } +} + +#[cfg(target_os = "linux")] +/// Is Debian OS? +fn is_debian_based() -> bool { + Command::new("sh") + .arg("-c") + .arg("grep -Ei 'debian|buntu' /etc/*release") + .output() + .map_or(false, |output| output.status.success()) +} + +#[cfg(target_os = "linux")] +/// Is Red Hat Package Manager? +fn is_rpm_based() -> bool { + Command::new("sh") + .arg("-c") + .arg("grep -Ei 'fedora|redhat|centos' /etc/*release") + .output() + .map_or(false, |output| output.status.success()) +} + +#[cfg(not(target_os = "linux"))] +/// Is Debian OS? +fn is_debian_based() -> bool { + false +} + +#[cfg(not(target_os = "linux"))] +/// Is Red Hat Package Manager? +fn is_rpm_based() -> bool { + false +} diff --git a/benches/core.rs b/benches/core.rs new file mode 100644 index 000000000..c312e354e --- /dev/null +++ b/benches/core.rs @@ -0,0 +1,372 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use spider::hashbrown::{HashMap, HashSet}; +use spider::packages::robotparser::parser::RobotFileParser; +use spider::utils::abs::{convert_abs_path, parse_absolute_url}; +use spider::utils::interner::ListBucket; +use spider::utils::trie::Trie; +use spider::utils::{ + clean_html_base, clean_html_full, clean_html_slim, detect_antibot_from_url, flip_http_https, + get_last_segment, prepare_url, +}; +use spider::CaseInsensitiveString; + +// --------------------------------------------------------------------------- +// Fixture generators +// --------------------------------------------------------------------------- + +/// Build a realistic HTML string with various elements. +fn generate_html(num_elements: usize) -> String { + let mut html = String::from( + "Bench\ + \ + \ + \ + \ +
", + ); + for i in 0..num_elements { + match i % 7 { + 0 => html.push_str(&format!( + "

Title {i}

Description for item {i} with some text.

" + )), + 1 => html.push_str(&format!( + "Link {i}" + )), + 2 => html.push_str(&format!( + "" + )), + 3 => html.push_str(&format!( + "" + )), + 4 => html.push_str(&format!( + "", + i % 50 + )), + 5 => html.push_str(&format!( + "\"Image" + )), + _ => html.push_str(&format!( + "

Paragraph {i} with bold and italic text.

" + )), + } + } + html.push_str("

Copyright 2025

"); + html +} + +/// Build a multi-agent robots.txt with Allow/Disallow rules. +fn generate_robots_txt(num_entries: usize) -> Vec { + let mut lines = Vec::with_capacity(num_entries + 10); + lines.push("User-agent: *".to_string()); + lines.push("Crawl-delay: 1".to_string()); + for i in 0..num_entries { + if i % 3 == 0 { + lines.push(format!("Disallow: /private/{i}/")); + } else { + lines.push(format!("Allow: /public/{i}/")); + } + } + lines.push(String::new()); + lines.push("User-agent: Googlebot".to_string()); + lines.push("Allow: /".to_string()); + lines.push(String::new()); + lines.push("User-agent: BadBot".to_string()); + lines.push("Disallow: /".to_string()); + lines +} + +/// Generate unique URL strings. +fn generate_urls(count: usize) -> Vec { + (0..count) + .map(|i| CaseInsensitiveString::from(format!("https://example.com/page/{i}"))) + .collect() +} + +// --------------------------------------------------------------------------- +// Group 1: HTML cleaning +// --------------------------------------------------------------------------- + +fn bench_html_cleaning(c: &mut Criterion) { + let mut group = c.benchmark_group("html-cleaning"); + + let sizes = [ + ("small-1KB", 8), + ("medium-50KB", 400), + ("large-200KB", 1600), + ]; + + for (label, count) in &sizes { + let html = generate_html(*count); + + group.bench_with_input(BenchmarkId::new("clean_html_base", label), &html, |b, h| { + b.iter(|| clean_html_base(black_box(h))) + }); + group.bench_with_input(BenchmarkId::new("clean_html_slim", label), &html, |b, h| { + b.iter(|| clean_html_slim(black_box(h))) + }); + group.bench_with_input(BenchmarkId::new("clean_html_full", label), &html, |b, h| { + b.iter(|| clean_html_full(black_box(h))) + }); + } + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 2: URL processing +// --------------------------------------------------------------------------- + +fn bench_url_processing(c: &mut Criterion) { + let mut group = c.benchmark_group("url-processing"); + + let base = parse_absolute_url("https://example.com/some/path/").unwrap(); + + group.bench_function("convert_abs_path/relative", |b| { + b.iter(|| convert_abs_path(black_box(&base), black_box("/products/item-42?ref=home"))) + }); + + group.bench_function("convert_abs_path/absolute", |b| { + b.iter(|| { + convert_abs_path( + black_box(&base), + black_box("https://other.example.org/landing"), + ) + }) + }); + + group.bench_function("flip_http_https", |b| { + b.iter(|| flip_http_https(black_box("https://example.com/page/123"))) + }); + + group.bench_function("prepare_url", |b| { + b.iter(|| prepare_url(black_box("example.com/page"))) + }); + + group.bench_function("get_last_segment", |b| { + b.iter(|| get_last_segment(black_box("/api/v2/users/profile"))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 3: Interner / ListBucket +// --------------------------------------------------------------------------- + +fn bench_interner(c: &mut Criterion) { + let mut group = c.benchmark_group("interner"); + + let urls_10k = generate_urls(10_000); + + group.bench_function("insert/10k", |b| { + b.iter(|| { + let mut bucket = ListBucket::new(); + for u in &urls_10k { + bucket.insert(black_box(u.clone())); + } + bucket + }) + }); + + // Pre-populate a bucket for lookup benchmarks + let mut populated = ListBucket::new(); + for u in &urls_10k { + populated.insert(u.clone()); + } + + group.bench_function("contains_hit/10k", |b| { + let needle = &urls_10k[5000]; + b.iter(|| populated.contains(black_box(needle))) + }); + + group.bench_function("contains_miss/10k", |b| { + let needle = CaseInsensitiveString::from("https://example.com/page/not-exists-99999"); + b.iter(|| populated.contains(black_box(&needle))) + }); + + group.bench_function("extend_links/1k_new_vs_10k_visited", |b| { + b.iter_batched( + || { + // Setup: clone the populated bucket, prepare new links + let bucket = populated.clone(); + let new_links: HashSet = (10_000..11_000) + .map(|i| CaseInsensitiveString::from(format!("https://example.com/page/{i}"))) + .collect(); + let pending: HashSet = HashSet::new(); + (bucket, pending, new_links) + }, + |(mut bucket, mut pending, new_links)| { + bucket.extend_links(black_box(&mut pending), black_box(new_links)); + (bucket, pending) + }, + criterion::BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 4: Robots.txt parser +// --------------------------------------------------------------------------- + +fn bench_robotparser(c: &mut Criterion) { + let mut group = c.benchmark_group("robotparser"); + + let lines_100 = generate_robots_txt(100); + + group.bench_function("parse/100_lines", |b| { + b.iter(|| { + let mut parser = RobotFileParser::new(); + parser.parse(black_box(&lines_100)); + parser + }) + }); + + // Pre-parse for can_fetch benchmarks + let mut parsed = RobotFileParser::new(); + parsed.modified(); + parsed.parse(&lines_100); + + group.bench_function("can_fetch/allowed", |b| { + b.iter(|| { + parsed.can_fetch( + black_box("spider"), + black_box("https://example.com/public/1/page"), + ) + }) + }); + + group.bench_function("can_fetch/blocked", |b| { + b.iter(|| { + parsed.can_fetch( + black_box("spider"), + black_box("https://example.com/private/0/secret"), + ) + }) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 5: Trie +// --------------------------------------------------------------------------- + +fn bench_trie(c: &mut Criterion) { + let mut group = c.benchmark_group("trie"); + + let paths: Vec = (0..1000) + .map(|i| format!("/section/{}/page/{}", i % 50, i)) + .collect(); + + group.bench_function("insert/1000_paths", |b| { + b.iter(|| { + let mut trie = Trie::new(); + for (i, p) in paths.iter().enumerate() { + trie.insert(black_box(p), black_box(i)); + } + trie + }) + }); + + // Pre-populate for search benchmarks + let mut populated_trie: Trie = Trie::new(); + for (i, p) in paths.iter().enumerate() { + populated_trie.insert(p, i); + } + + group.bench_function("search/hit", |b| { + b.iter(|| populated_trie.search(black_box("/section/25/page/500"))) + }); + + group.bench_function("search/miss", |b| { + b.iter(|| populated_trie.search(black_box("/nonexistent/path/here"))) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 6: Antibot detection +// --------------------------------------------------------------------------- + +fn bench_antibot(c: &mut Criterion) { + let mut group = c.benchmark_group("antibot-detection"); + + group.bench_function("detect_antibot_url/clean", |b| { + b.iter(|| detect_antibot_from_url(black_box("https://example.com/products/shoes"))) + }); + + group.bench_function("detect_antibot_url/cloudflare", |b| { + b.iter(|| { + detect_antibot_from_url(black_box( + "https://example.com/cdn-cgi/challenge-platform/scripts/managed/init.js", + )) + }) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Group 7: CSS extraction (spider_utils) +// --------------------------------------------------------------------------- + +fn bench_css_extraction(c: &mut Criterion) { + let mut group = c.benchmark_group("css-extraction"); + + // Build selector map: 10 selectors + let mut selector_map: HashMap> = HashMap::new(); + let selectors = [ + ("titles", "h1, h2, h3"), + ("links", "a[href]"), + ("images", "img"), + ("paragraphs", "p"), + ("divs", "div.card"), + ("scripts", "script[src]"), + ("meta", "meta[name]"), + ("lists", "ul, ol"), + ("forms", "form"), + ("nav", "nav a"), + ]; + for (key, sel) in &selectors { + selector_map.insert(key.to_string(), vec![sel.to_string()]); + } + + group.bench_function("build_selectors/10_selectors", |b| { + b.iter(|| { + spider_utils::build_selectors_base::>(black_box( + selector_map.clone(), + )) + }) + }); + + let doc_selectors = + spider_utils::build_selectors_base::>(selector_map.clone()); + let html_50kb = generate_html(400); + + group.bench_function("css_query_select_map/10_selectors_on_50kb", |b| { + b.iter(|| { + spider_utils::css_query_select_map(black_box(&html_50kb), black_box(&doc_selectors)) + }) + }); + + group.finish(); +} + +// --------------------------------------------------------------------------- +// Criterion harness +// --------------------------------------------------------------------------- + +criterion_group!( + benches, + bench_html_cleaning, + bench_url_processing, + bench_interner, + bench_robotparser, + bench_trie, + bench_antibot, + bench_css_extraction, +); +criterion_main!(benches); diff --git a/benches/crawl.rs b/benches/crawl.rs new file mode 100644 index 000000000..a6b9213d0 --- /dev/null +++ b/benches/crawl.rs @@ -0,0 +1,105 @@ +pub mod go_colly; +pub mod node_crawler; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use spider::website::Website; +use std::{env, process::Command}; + +/// bench crawling between different libs +pub fn bench_speed(c: &mut Criterion) { + let mut worker: Option = None; + let query = env::var("SPIDER_BENCH_URL").unwrap_or("https://spider.cloud".into()); + + if cfg!(feature = "decentralized") { + worker.replace( + std::process::Command::new("spider_worker") + .spawn() + .expect("spider_worker command failed to start"), + ); + } + + let mut group = c.benchmark_group("crawl-speed/libraries"); + let sample_count = 10; + + let sample_title = format!("crawl {} samples", sample_count); + + group.sample_size(sample_count); + + let rt = spider::tokio::runtime::Runtime::new().unwrap(); + + group.bench_function(format!("Rust[spider]: {}", sample_title), |b| { + let mut website = Website::new(&query); + // try to only enable when you know you can or have permission from the website to avoid being blocked. + website.configuration.respect_robots_txt = false; + + b.to_async(&rt) + .iter(|| black_box(async_crawl_single(website.clone()))) + }); + + drop(rt); + if let Some(mut worker) = worker.take() { + worker.kill().expect("spider_worker wasn't running"); + }; + drop(worker); + + // [deprecated] - lib does not update and over 60x slower. + if env::var("BENCH_GO_COLLY").unwrap_or_default() == "true" { + group.bench_function(format!("Go[colly]: {}", sample_title), |b| { + b.iter(|| { + black_box( + Command::new("./gospider") + .arg(&query) + .output() + .expect("go command failed to start"), + ) + }) + }); + } + + // nodejs runs the cpu fully to 100%. Small test can run fast - does not scale with concurrency. + if env::var("BENCH_NODE_CRAWLER").unwrap_or_default() == "true" { + group.bench_function(format!("Node.js[crawler]: {}", sample_title), |b| { + b.iter(|| { + black_box( + Command::new("node") + .arg("./node-crawler.js") + .arg(&query) + .output() + .expect("node command failed to start"), + ) + }) + }); + } + + // network latency drawbacks - only compare locale examples. + if env::var("BENCH_C_WGET").unwrap_or_default() == "true" { + group.bench_function(format!("C[wget]: {}", sample_title), |b| { + b.iter(|| { + black_box( + Command::new("wget") + .args([ + "-4", + "--recursive", + "--no-parent", + "--ignore-tags=img,link,script", + "--spider", + "-q", + &query, + ]) + .output() + .expect("wget command failed to start"), + ) + }) + }); + } + + group.finish(); +} + +/// crawl threaded internal test single +async fn async_crawl_single(mut website: Website) { + website.crawl().await; +} + +criterion_group!(benches, bench_speed); +criterion_main!(benches); diff --git a/benches/go_colly.rs b/benches/go_colly.rs new file mode 100644 index 000000000..a1fb7349a --- /dev/null +++ b/benches/go_colly.rs @@ -0,0 +1,47 @@ +use std::fs::File; +use std::io::{BufWriter, Write}; + +pub fn crawl_stub() -> String { + r#" + package main + + import ( + "github.com/gocolly/colly/v2" + "os" + ) + + func main() { + c := colly.NewCollector() + u := "https://spider.cloud" + + if len(os.Args) > 1 { + u = os.Args[1] + } + + c.Limit(&colly.LimitRule{ + Delay: 0, + }) + + c.OnHTML(`a[href^="/"]:not([href$=".png"]):not([href$=".jpg"]):not([href$=".mp4"]):not([href$=".mp3"]):not([href$=".gif"]), + a[href^="` + u + `"]:not([href$=".png"]):not([href$=".jpg"]):not([href$=".mp4"]):not([href$=".mp3"]):not([href$=".gif"])`, func(e *colly.HTMLElement) { + e.Request.Visit(e.Attr("href")) + }) + + c.OnRequest(func(r *colly.Request) { + }) + + c.Visit(u) + } + "#.to_string() +} + +pub fn gen_crawl() -> String { + let crawl_script = String::from("./go-colly.go"); + let file = File::create(&crawl_script).expect("create go crawl script"); + let mut file = BufWriter::new(file); + let stub = crawl_stub(); + file.write_all(stub.as_bytes()) + .expect("Unable to write data"); + + crawl_script +} diff --git a/benches/node_crawler.rs b/benches/node_crawler.rs new file mode 100644 index 000000000..ad2f3e25d --- /dev/null +++ b/benches/node_crawler.rs @@ -0,0 +1,63 @@ +use std::fs::File; +use std::io::{BufWriter, Write}; + +pub fn crawl_stub() -> String { + r#" + const Crawler = require("crawler"); + const { argv } = require('node:process'); + + const base = argv.slice(1)[0] || "https://spider.cloud"; + const crawledPages = { [base]: true }; + const ignoreSelector = `:not([href$=".png"]):not([href$=".jpg"]):not([href$=".mp4"]):not([href$=".mp3"]):not([href$=".gif"])`; + + const crawlOptions = { + skipEventRequest: false, + }; + + const callback = (error, res) => { + if (error) { + console.error(error); + } else { + const $ = res.$; + + $(`a[href^="/"]${ignoreSelector},a[href^="${base}"]${ignoreSelector}`).each( + (_i, elem) => { + if (!crawledPages[elem.attribs.href]) { + crawledPages[elem.attribs.href] = true; + directCrawl(`${base}${elem.attribs.href}`); + } + } + ); + } + }; + + const crawler = new Crawler({ + maxConnections: 10, + rateLimit: 0, + callback, + }); + + const directCrawl = (uri) => { + crawler.direct({ + uri, + callback, + ...crawlOptions, + }); + }; + + directCrawl(base); + + "#.to_string() +} + +pub fn gen_crawl() -> String { + let crawl_script = String::from("./node-crawler.js"); + let file = File::create(&crawl_script).expect("create js crawl script"); + let mut file = BufWriter::new(file); + let stub = crawl_stub(); + + file.write_all(stub.as_bytes()) + .expect("Unable to write data"); + + crawl_script +} diff --git a/crates.bzl b/crates.bzl new file mode 100644 index 000000000..9da7878e3 --- /dev/null +++ b/crates.bzl @@ -0,0 +1,48 @@ +""" +Crate universe configuration for Spider project. +This file defines the external Rust crate dependencies from crates.io. +Generated dependencies are resolved from Cargo.lock. +""" + +load("@rules_rust//crate_universe:defs.bzl", "crates_repository") + +def crate_repositories(): + """ + Define all external crate dependencies from crates.io. + Uses crate_universe to generate Bazel targets from Cargo metadata. + """ + crates_repository( + name = "crates_io", + # The lockfile contains exact versions of all dependencies + cargo_lockfile = "//:Cargo.lock", + # All Cargo.toml manifests in the workspace + manifests = [ + "//:Cargo.toml", + "//spider:Cargo.toml", + "//spider_cli:Cargo.toml", + "//spider_worker:Cargo.toml", + "//spider_agent:Cargo.toml", + "//spider_agent_types:Cargo.toml", + "//spider_agent_html:Cargo.toml", + "//spider_utils:Cargo.toml", + "//benches:Cargo.toml", + "//examples:Cargo.toml", + ], + # Optional: pinned versions for reproducibility + # These can be overridden for security updates + supported_targets = [ + "aarch64-apple-darwin", + "aarch64-apple-ios", + "aarch64-linux-android", + "aarch64-unknown-linux-gnu", + "i686-apple-darwin", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "wasm32-unknown-unknown", + "wasm32-wasi", + "x86_64-apple-darwin", + "x86_64-apple-ios", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + ], + ) \ No newline at end of file diff --git a/default.nix b/default.nix new file mode 100644 index 000000000..232154691 --- /dev/null +++ b/default.nix @@ -0,0 +1,27 @@ +{ pkgs ? import {} }: + +let + spider = pkgs.rustPlatform.buildRustPackage { + pname = "spider"; + version = "2.43.20"; + + src = ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + }; + + nativeBuildInputs = []; + + meta = with pkgs.lib; { + description = "A web crawler and scraper, building blocks for data curation workloads."; + homepage = "https://github.com/spider-rs/spider"; + license = licenses.mit; + maintainers = with maintainers; [ j-mendez ]; + platforms = platforms.all; + }; + }; +in +{ + inherit spider; +} diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel new file mode 100644 index 000000000..585c5ed10 --- /dev/null +++ b/examples/BUILD.bazel @@ -0,0 +1,170 @@ +""" +Spider Examples - Example code demonstrating Spider usage. +""" + +load("@rules_rust//rust:defs.bzl", "rust_binary") + +package(default_visibility = ["//visibility:public"]) + +# Basic example +rust_binary( + name = "example", + srcs = ["example.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Scrape example +rust_binary( + name = "scrape", + srcs = ["scrape.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Debug example +rust_binary( + name = "debug", + srcs = ["debug.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Download example +rust_binary( + name = "download", + srcs = ["download.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Serde example +rust_binary( + name = "serde_example", + srcs = ["serde.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + "@crates_io//:serde_json", + "@crates_io//:flexbuffers", + ], + rustc_flags = [ + "--cfg", + 'feature="serde"', + ], +) + +# Callback example +rust_binary( + name = "callback", + srcs = ["callback.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Configuration example +rust_binary( + name = "configuration", + srcs = ["configuration.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Blacklist example +rust_binary( + name = "blacklist", + srcs = ["blacklist.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Whitelist example +rust_binary( + name = "whitelist", + srcs = ["whitelist.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Advanced configuration example +rust_binary( + name = "advanced_configuration", + srcs = ["advanced_configuration.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Queue example +rust_binary( + name = "queue", + srcs = ["queue.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# CSS scrape example +rust_binary( + name = "css_scrape", + srcs = ["css_scrape.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "//spider_utils:spider_utils", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) + +# Subscribe download example +rust_binary( + name = "subscribe_download", + srcs = ["subscribe_download.rs"], + edition = "2021", + deps = [ + "//spider:spider", + "@crates_io//:tokio", + "@crates_io//:env_logger", + ], +) \ No newline at end of file diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 000000000..ea8e7a040 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,350 @@ +[package] +name = "spider_examples" +version = "2.37.105" +authors = ["j-mendez "] +description = "Multithreaded web crawler written in Rust." +repository = "https://github.com/spider-rs/spider" +readme = "README.md" +keywords = ["crawler", "spider"] +categories = ["web-programming"] +license = "MIT" +documentation = "https://docs.rs/spider" +publish = false +edition = "2021" + +[badges] +maintenance = { status = "as-is" } + +[dev-dependencies] +convert_case = "0.6.0" +env_logger = "0.11.3" +htr = "0.5.27" +flexbuffers = "25" +console-subscriber = { version = "0.4" } +serde_json = "1" + +[dependencies.spider] +version = "2" +path = "../spider" +features = ["serde"] + +[dependencies.spider_utils] +version = "2" + +[dependencies.spider_transformations] +version = "2" + +[[example]] +name = "example" +path = "example.rs" + +[[example]] +name = "scrape" +path = "scrape.rs" + +[[example]] +name = "debug" +path = "debug.rs" + +[[example]] +name = "download" +path = "download.rs" + +[[example]] +name = "download_to_react" +path = "download_to_react.rs" + +[[example]] +name = "serde" +path = "serde.rs" + +[[example]] +name = "subscribe" +path = "subscribe.rs" +required-features = ["spider/sync"] + +[[example]] +name = "subscribe_multiple" +path = "subscribe_multiple.rs" +required-features = ["spider/sync"] + +[[example]] +name = "loop" +path = "loop.rs" +required-features = ["spider/sync", "spider/smart"] + +[[example]] +name = "cache" +path = "cache.rs" +required-features = ["spider/sync", "spider/cache"] + +[[example]] +name = "cache_chrome_hybrid" +path = "cache_chrome_hybrid.rs" +required-features = [ + "spider/sync", + "spider/chrome", + "spider/cache_chrome_hybrid", +] + +[[example]] +name = "cache_remote_skip_browser" +path = "cache_remote_skip_browser.rs" +required-features = [ + "spider/sync", + "spider/chrome", + "spider/chrome_remote_cache", +] + +[[example]] +name = "rss" +path = "rss.rs" +required-features = ["spider/sync"] + +[[example]] +name = "callback" +path = "callback.rs" + +[[example]] +name = "sitemap" +path = "sitemap.rs" +required-features = ["spider/sitemap"] + +[[example]] +name = "sitemap_only" +path = "sitemap_only.rs" +required-features = ["spider/sitemap"] + +[[example]] +name = "configuration" +path = "configuration.rs" + +[[example]] +name = "blacklist" +path = "blacklist.rs" + +[[example]] +name = "whitelist" +path = "whitelist.rs" + +[[example]] +name = "budget" +path = "budget.rs" +required-features = ["spider/sync"] + +[[example]] +name = "depth" +path = "depth.rs" +required-features = ["spider/sync"] + +[[example]] +name = "cron" +path = "cron.rs" +required-features = ["spider/sync", "spider/cron"] + +[[example]] +name = "chrome" +path = "chrome.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "chrome_remote" +path = "chrome_remote.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "concurrent_profiles" +path = "concurrent_profiles.rs" +required-features = ["spider/sync", "spider/chrome", "spider/disk"] + +[[example]] +name = "anti_bots" +path = "anti_bots.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "chrome_remote_tls" +path = "chrome_remote_tls.rs" +required-features = [ + "spider/sync", + "spider/chrome", + "spider/chrome_tls_connection", +] + +[[example]] +name = "chrome_sendable" +path = "chrome_sendable.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "real_world" +path = "real_world.rs" +required-features = [ + "spider/sync", + "spider/chrome" +] + +[[example]] +name = "chrome_viewport" +path = "chrome_viewport.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "chrome_screenshot" +path = "chrome_screenshot.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "chrome_screenshot_with_config" +path = "chrome_screenshot_with_config.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "chrome_web_automation" +path = "chrome_web_automation.rs" +required-features = ["spider/sync", "spider/chrome"] + +[[example]] +name = "authenticated_page" +path = "authenticated_page.rs" +required-features = ["spider/sync", "spider/cookies", "spider/chrome"] + +[[example]] +name = "smart" +path = "smart.rs" +required-features = ["spider/sync", "spider/smart"] + +[[example]] +name = "openai" +path = "openai.rs" +required-features = ["spider/sync", "spider/openai"] + +[[example]] +name = "openai_multi" +path = "openai_multi.rs" +required-features = ["spider/sync", "spider/openai"] + +[[example]] +name = "openai_extra" +path = "openai_extra.rs" +required-features = ["spider/sync", "spider/openai"] + +[[example]] +name = "openai_cache" +path = "openai_cache.rs" +required-features = ["spider/sync", "spider/openai", "spider/cache_openai"] + +[[example]] +name = "gemini" +path = "gemini.rs" +required-features = ["spider/sync", "spider/gemini"] + +[[example]] +name = "encoding" +path = "encoding.rs" +required-features = ["spider/sync", "spider/encoding"] + +[[example]] +name = "auto_encoding" +path = "auto_encoding.rs" +required-features = ["spider/sync", "spider/encoding"] + +[[example]] +name = "advanced_configuration" +path = "advanced_configuration.rs" +required-features = ["spider/sync"] + +[[example]] +name = "url_glob" +path = "url_glob.rs" +required-features = ["spider/sync", "spider/glob"] + +[[example]] +name = "url_glob_subdomains" +path = "url_glob_subdomains.rs" +required-features = ["spider/sync", "spider/glob"] + +[[example]] +name = "subscribe_download" +path = "subscribe_download.rs" +required-features = ["spider/sync"] + +[[example]] +name = "queue" +path = "queue.rs" +required-features = ["spider/sync"] + +[[example]] +name = "css_scrape" +path = "css_scrape.rs" +required-features = ["spider/sync"] + +[[example]] +name = "webdriver" +path = "webdriver.rs" +required-features = ["spider/sync", "spider/webdriver", "spider/webdriver_stealth"] + +[[example]] +name = "webdriver_remote" +path = "webdriver_remote.rs" +required-features = ["spider/sync", "spider/webdriver", "spider/webdriver_stealth"] + +[[example]] +name = "webdriver_screenshot" +path = "webdriver_screenshot.rs" +required-features = ["spider/sync", "spider/webdriver", "spider/webdriver_screenshot"] + +[[example]] +name = "remote_multimodal" +path = "remote_multimodal.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_scrape" +path = "remote_multimodal_scrape.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_benchmark" +path = "remote_multimodal_benchmark.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_multi" +path = "remote_multimodal_multi.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_dual" +path = "remote_multimodal_dual.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_dual_automation" +path = "remote_multimodal_dual_automation.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_quotes" +path = "remote_multimodal_quotes.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "remote_multimodal_listing" +path = "remote_multimodal_listing.rs" +required-features = ["spider/sync", "spider/chrome", "spider/agent_chrome"] + +[[example]] +name = "not_a_robot" +path = "not_a_robot.rs" +required-features = ["spider/sync", "spider/chrome", "spider/chrome_headed", "spider/agent_chrome", "spider/agent_skills"] + +[[example]] +name = "not_a_robot_haiku" +path = "not_a_robot_haiku.rs" +required-features = ["spider/sync", "spider/chrome", "spider/chrome_headed", "spider/agent_chrome", "spider/agent_skills"] + +[[example]] +name = "not_a_robot_chrome_ai" +path = "not_a_robot_chrome_ai.rs" +required-features = ["spider/sync", "spider/chrome", "spider/chrome_headed", "spider/agent_chrome", "spider/agent_skills"] diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..8b3ae15f2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,157 @@ +# Examples + +First `git clone https://github.com/spider-rs/spider.git` and `cd spider`. Use the release flag for the best performance `--release` when running the examples below. +It is recommended to use the [headless-browser](https://github.com/spider-rs/headless-browser) project for web crawling and scraping via a headless Docker container or by launching your own local [chrome-headless-shell](https://developer.chrome.com/blog/chrome-headless-shell) when using the chrome examples. + +## Basic + +Simple concurrent crawl [Simple](./example.rs). + +- `cargo run --example example` + +Subscribe to realtime changes [Subscribe](./subscribe.rs). + +- `cargo run --example subscribe` + +Subscribe to realtime changes [Subscribe](./subscribe_multiple.rs). + +- `cargo run --example subscribe_multiple` + +Live handle index mutation example [Callback](./callback.rs). + +- `cargo run --example callback` + +Enable log output [Debug](./debug.rs). + +- `cargo run --example debug` + +Scrape the webpage with and gather html [Scrape](./scrape.rs). + +- `cargo run --example scrape` + +Scrape and download the html file to fs [Download HTML](./download.rs). \*Note: Enable feature flag [full_resources] to gather all files like css, jss, and etc. + +- `cargo run --example download` + +Scrape and download html to react components and store to fs [Download to React Component](./download.rs). + +- `cargo run --example download_to_react` + +Crawl the page and output the links via [Serde](./serde.rs). + +- `cargo run --example serde --features serde` + +Crawl links with a budget of amount of pages allowed [Budget](./budget.rs). + +- `cargo run --example budget` + +Crawl links at a given cron time [Cron](./cron.rs). + +- `cargo run --example cron` + +Crawl links with chrome headless rendering [Chrome](./chrome.rs). + +- `cargo run --example chrome --features chrome` + +Crawl links with chrome headed rendering [Chrome](./chrome.rs). + +- `cargo run --example chrome --features chrome_headed` + +Crawl a logged-in page by reusing a dedicated local Chrome/Chromium profile on Ubuntu Desktop, or fall back to cookie injection [Authenticated Page](./authenticated_page.rs). + +- `PREPARE_PROFILE=1 CHROME_USER_DATA_DIR="$HOME/.local/share/spider/login-chrome-profile" cargo run --example authenticated_page --features="spider/sync spider/cookies spider/chrome"` +- `CHROME_USER_DATA_DIR="$HOME/.local/share/spider/login-chrome-profile" cargo run --example authenticated_page --features="spider/sync spider/cookies spider/chrome" -- https://example.com/protected/page` +- `OUTPUT_DIR=authenticated_page_output CHROME_USER_DATA_DIR="$HOME/.local/share/spider/login-chrome-profile" cargo run --example authenticated_page --features="spider/sync spider/cookies spider/chrome" -- https://example.com/protected/page` +- `COOKIE='sessionid=...; csrftoken=...' cargo run --example authenticated_page --features="spider/sync spider/cookies spider/chrome" -- https://example.com/protected/page` + +This example saves rendered HTML, extracted text, and downloaded page images into the output directory. + +Crawl links with chrome headless rendering remote connections [Chrome](./chrome.rs). + +- `cargo run --example chrome_remote --features chrome` + +Crawl links with view port configuration [Chrome Viewport](./chrome_viewport.rs). + +- `cargo run --example chrome_viewport --features chrome` + +Take a screenshot of a page during crawl [Chrome Screenshot](./chrome_screenshot.rs). + +- `cargo run --example chrome_screenshot --features="spider/sync spider/chrome spider/chrome_store_page"` + +Crawl links with smart mode detection. Runs HTTP by default until Chrome Rendering is needed. [Smart](./smart.rs). + +- `cargo run --example smart --features smart` + +Use different encodings for the page. [Encoding](./encoding.rs). + +- `cargo run --example encoding --features encoding` + +Use advanced configuration re-use. [Advanced Configuration](./advanced_configuration.rs). + +- `cargo run --example cache_chrome_hybrid --features="spider/sync spider/chrome spider/cache_chrome_hybrid"` + +Use chrome hybrid caching. [Chrome Cache Hybrid](./cache_chrome_hybrid.rs). + +- `cargo run --example advanced_configuration` + +End-to-end remote cache warm + skip-browser return path using `HYBRID_CACHE_ENDPOINT` (index_cache_server / hybrid_cache_server). [Remote Cache Skip Browser](./cache_remote_skip_browser.rs). + +- `HYBRID_CACHE_ENDPOINT=http://127.0.0.1:8080 cargo run --example cache_remote_skip_browser --features="spider/sync spider/chrome spider/chrome_remote_cache" -- https://example.com/` + +Use URL globbing for a domain. [URL Globbing](./url_glob.rs). + +- `cargo run --example glob --features glob` + +Use URL globbing for a domain and subdomains. [URL Globbing Subdomains](./url_glob_subdomains.rs). + +- `cargo run --example url_glob_subdomains --features glob` + +Downloading files in a subscription. [Subscribe Download](./subscribe_download.rs). + +- `cargo run --example subscribe_download` + +Add links to gather mid crawl. [Queue](./queue.rs). + +- `cargo run --example queue` + +Use OpenAI to get custom Javascript to run in a browser. [OpenAI](./openai.rs). Make sure to set OPENAI_API_KEY=$MY_KEY as an env variable or pass it in before the script. + +- `cargo run --example openai` + +or + +- `OPENAI_API_KEY=replace_me_with_key cargo run --example openai` + +or setting multiple actions to drive the browser + +- `OPENAI_API_KEY=replace_me_with_key cargo run --example openai_multi` + +or to get custom data from the GPT with JS scripts if needed. + +- `OPENAI_API_KEY=replace_me_with_key cargo run --example openai_extra` + +## Remote Multimodal (OpenRouter / Vision+Text) + +Single page extraction from a book details page [Remote Multimodal Scrape](./remote_multimodal_scrape.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_scrape --features "spider/sync spider/chrome spider/agent_chrome"` + +Multi-page extraction by crawling from a category page [Remote Multimodal Multi](./remote_multimodal_multi.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_multi --features "spider/sync spider/chrome spider/agent_chrome"` + +Dual-model routing (vision + text model split) [Remote Multimodal Dual](./remote_multimodal_dual.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_dual --features "spider/sync spider/chrome spider/agent_chrome"` + +Dual-model multi-round automation [Remote Multimodal Dual Automation](./remote_multimodal_dual_automation.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_dual_automation --features "spider/sync spider/chrome spider/agent_chrome"` + +Quote extraction with structured JSON output [Remote Multimodal Quotes](./remote_multimodal_quotes.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_quotes --features "spider/sync spider/chrome spider/agent_chrome"` + +Listing page product extraction [Remote Multimodal Listing](./remote_multimodal_listing.rs). + +- `OPEN_ROUTER=replace_me_with_key cargo run --example remote_multimodal_listing --features "spider/sync spider/chrome spider/agent_chrome"` diff --git a/examples/advanced_configuration.rs b/examples/advanced_configuration.rs new file mode 100644 index 000000000..a16481fef --- /dev/null +++ b/examples/advanced_configuration.rs @@ -0,0 +1,70 @@ +//! `cargo run --example advanced_configuration` +extern crate spider; + +use spider::{configuration::Configuration, tokio, website::Website}; +use std::{io::Error, time::Instant}; + +const CAPACITY: usize = 5; +const CRAWL_LIST: [&str; CAPACITY] = [ + "https://rsseau.fr/en", + "https://choosealicense.com", + "https://jeffmendez.com", + "https://spider-rs.github.io/spider-nodejs/", + "https://spider-rs.github.io/spider-py/", +]; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let config = Configuration::new() + .with_user_agent(Some("SpiderBot")) + .with_blacklist_url(Some(Vec::from(["https://rsseau.fr/resume".into()]))) + .with_subdomains(false) + .with_tld(false) + .with_redirect_limit(3) + .with_respect_robots_txt(true) + .with_external_domains(Some( + Vec::from(["http://loto.rsseau.fr/"].map(|d| d.to_string())).into_iter(), + )) + .build(); + + let mut handles = Vec::with_capacity(CAPACITY); + + for website_url in CRAWL_LIST { + match Website::new(website_url) + .with_config(config.to_owned()) + .build() + { + Ok(mut website) => { + let handle = tokio::spawn(async move { + println!("Starting Crawl - {:?}", website.get_url().inner()); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "{:?} - Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + website.get_url().inner(), + duration, + links.len() + ); + }); + + handles.push(handle); + } + Err(e) => println!("{:?}", e), + } + } + + for handle in handles { + let _ = handle.await; + } + + Ok(()) +} diff --git a/examples/anti_bots.rs b/examples/anti_bots.rs new file mode 100644 index 000000000..384b6e5e1 --- /dev/null +++ b/examples/anti_bots.rs @@ -0,0 +1,122 @@ +//! cargo run --example anti_bots --features="chrome chrome_intercept real_browser" + +use spider::configuration::{ChromeEventTracker, Fingerprint}; +use spider::features::chrome_common::{ + RequestInterceptConfiguration, WaitForDelay, WaitForIdleNetwork, WaitForSelector, +}; +use spider::features::chrome_viewport; +use spider::tokio; +use spider::tokio::io::AsyncWriteExt; +use spider::website::Website; +use std::fs::create_dir_all; +use std::time::Duration; + +async fn crawl_website(url: &str) { + let mut stdout: tokio::io::Stdout = tokio::io::stdout(); + let mut interception = RequestInterceptConfiguration::new(true); + let mut tracker = ChromeEventTracker::default(); + + interception.block_javascript = false; + interception.block_stylesheets = false; + interception.block_visuals = false; + interception.block_ads = false; + interception.block_analytics = true; + + tracker.responses = true; + tracker.requests = true; + + let screenshot_params: spider::configuration::ScreenshotParams = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let viewport = chrome_viewport::randomize_viewport(&chrome_viewport::DeviceType::Desktop); + + let creepjs = url == "https://abrahamjuliot.github.io/creepjs/"; + + let mut website: Website = Website::new(url) + .with_limit(1) + .with_retry(0) + .with_chrome_intercept(interception) + .with_wait_for_delay(Some(WaitForDelay::new(Some(Duration::from_millis(if creepjs { 10_000 } else { 200 }))))) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_millis(2000))))) + .with_wait_for_idle_dom(Some(WaitForSelector::new( + Some(Duration::from_millis(5000)), + if creepjs { "fingerprint".into() } else { "body".into() }, + ))) + .with_screenshot(Some(screenshot_config)) + .with_block_assets(true) + .with_viewport(Some(viewport)) + .with_user_agent(Some("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")) + .with_stealth(true) + .with_return_page_links(true) + .with_event_tracker(Some(tracker)) + .with_fingerprint_advanced(Fingerprint::None) + .with_proxies(Some(vec!["http://localhost:8888".into()])) + .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + let mut g = website.subscribe_guard().unwrap(); + g.guard(true); + + let start = crate::tokio::time::Instant::now(); + + let (links, _) = tokio::join!( + async move { + website.crawl().await; + website.unsubscribe(); + website.get_all_links_visited().await + }, + async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "---- {}\nBytes transferred {:?}\nHTML Size {:?}\nLinks: {:?}\nRequests Sent {:?}\nStatus: {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len(), + match page.page_links { + Some(ref l) => l.len(), + _ => 0, + }, + page.get_request().as_ref().map(|f| f.len()), + page.status_code + ) + .as_bytes(), + ) + .await; + + g.inc(); + } + } + ); + + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + url, + duration, + links.len() + ); +} + +#[tokio::main] +async fn main() { + env_logger::init(); + let _ = create_dir_all("./storage"); + + let _ = tokio::join!( + crawl_website("https://demo.fingerprint.com/playground"), + crawl_website("https://bot-detector.rebrowser.net"), + crawl_website("https://www.browserscan.net/bot-detection"), + crawl_website("https://deviceandbrowserinfo.com/info_device"), + crawl_website("https://deviceandbrowserinfo.com/are_you_a_bot"), + crawl_website("https://abrahamjuliot.github.io/creepjs/"), + crawl_website("https://bot.sannysoft.com/"), + crawl_website("https://fingerprint-scan.com/"), + ); +} diff --git a/examples/authenticated_page.rs b/examples/authenticated_page.rs new file mode 100644 index 000000000..0c08960f2 --- /dev/null +++ b/examples/authenticated_page.rs @@ -0,0 +1,794 @@ +//! Crawl a logged-in page with a reusable local browser profile and save rendered HTML plus +//! extracted text blocks. +//! +//! Recommended Ubuntu Desktop flow: +//! 1. Launch a dedicated persistent Chrome/Chromium profile and log in once: +//! +//! ```bash +//! PREPARE_PROFILE=1 \ +//! CHROME_USER_DATA_DIR="$HOME/.local/share/spider/login-chrome-profile" \ +//! cargo run --example authenticated_page \ +//! --features="spider/sync spider/cookies spider/chrome" +//! ``` +//! +//! 2. After logging into the target site in that browser window, rerun the crawl: +//! +//! ```bash +//! CHROME_USER_DATA_DIR="$HOME/.local/share/spider/login-chrome-profile" \ +//! cargo run --example authenticated_page \ +//! --features="spider/sync spider/cookies spider/chrome" \ +//! -- 'https://example.com/protected/page' +//! ``` +//! +//! Legacy cookie mode is still available: +//! +//! ```bash +//! COOKIE='sessionid=...; csrftoken=...' \ +//! cargo run --example authenticated_page \ +//! --features="spider/sync spider/cookies spider/chrome" \ +//! -- 'https://example.com/protected/page' +//! ``` +//! +//! Optional environment variables: +//! - TARGET_URL: fallback target when no CLI arg is provided +//! - OUTPUT_DIR: directory for crawl artifacts, default `authenticated_page_output` +//! - OUTPUT_HTML: where to save the rendered HTML +//! - OUTPUT_JSON: where to save extracted text blocks +//! - DOWNLOAD_IMAGES: set to `0` to skip downloading image assets +//! - IMAGE_SELECTORS: selectors used to collect image URLs, default `img` +//! - USER_AGENT: override the browser-like user agent header +//! - ACCEPT_LANGUAGE_HEADER: override Accept-Language +//! - REFERER_URL: optional Referer header +//! - TITLE_SELECTORS: comma-separated CSS/XPath selectors +//! - CONTENT_SELECTORS: comma-separated CSS/XPath selectors +//! - WAIT_FOR_SELECTOR: CSS selector list to wait for before capture +//! - COOKIE: optional cookie string fallback +//! - CHROME_CONNECTION_URL: existing CDP endpoint like http://127.0.0.1:9222/json/version +//! - CHROME_USER_DATA_DIR: persistent local browser user-data-dir to reuse +//! - CHROME_PROFILE_DIR: Chrome profile directory inside the user-data-dir, default `Default` +//! - CHROME_BIN: browser binary path or executable name +//! - CHROME_DEBUGGING_PORT: local debugging port for the dedicated browser, default `9222` +//! - CHROME_START_URL: page to open when preparing or launching the dedicated browser +//! - CHROME_HEADLESS: set to `1` to launch the dedicated browser in headless mode +//! - CHROME_EXTRA_ARGS: extra Chromium args split by shell whitespace +//! - CHROME_PROXY: explicit proxy for the dedicated browser, like `http://127.0.0.1:7890` +//! - CHROME_INHERIT_PROXY: set to `1` to inherit terminal proxy env vars instead of clearing them +//! - PREPARE_PROFILE: set to `1` to launch/reuse the local browser and exit for manual login + +extern crate env_logger; +extern crate spider; + +use env_logger::Env; +use spider::client::header::{ + HeaderMap, HeaderValue, ACCEPT_LANGUAGE, CONTENT_TYPE, COOKIE as COOKIE_HEADER, REFERER, + USER_AGENT, +}; +use spider::configuration::{WaitForIdleNetwork, WaitForSelector}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::hashbrown::{HashMap, HashSet}; +use spider::page::Page; +use spider::reqwest::{Client, Url}; +use spider::tokio; +use spider::website::Website; +use spider_utils::build_selectors; +use std::env; +use std::fs; +use std::io::{Error, ErrorKind, Result}; +use std::net::{SocketAddr, TcpStream}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; +use std::thread; +use std::time::Duration; + +const DEFAULT_TARGET_URL: &str = "https://example.com/"; +const DEFAULT_START_URL: &str = "https://example.com/"; +const DEFAULT_WAIT_SELECTOR: &str = "body"; +const DEFAULT_PROFILE_DIR: &str = "Default"; +const DEFAULT_DEBUGGING_PORT: u16 = 9222; +const DEFAULT_OUTPUT_DIR: &str = "authenticated_page_output"; + +fn split_selectors(value: &str) -> HashSet { + value + .split(',') + .map(str::trim) + .filter(|item| !item.is_empty()) + .map(ToOwned::to_owned) + .collect() +} + +fn extraction_selectors() -> spider_utils::DocumentSelectors { + let title = env::var("TITLE_SELECTORS").unwrap_or_else(|_| "title,h1,h2".to_string()); + let content = env::var("CONTENT_SELECTORS").unwrap_or_else(|_| { + [ + "main", + "article", + "[role='main']", + ".content", + ".main", + ".article", + ".post-content", + ".entry-content", + ".article-content", + ".rich-text", + ".RichContent", + ".RichText", + "body", + ] + .join(",") + }); + let images = env::var("IMAGE_SELECTORS").unwrap_or_else(|_| "img".to_string()); + + let mut selector_map: HashMap> = HashMap::new(); + selector_map.insert("title".to_string(), split_selectors(&title)); + selector_map.insert("content".to_string(), split_selectors(&content)); + selector_map.insert("images".to_string(), split_selectors(&images)); + + build_selectors(selector_map) +} + +fn json_error(err: serde_json::Error) -> Error { + Error::other(err) +} + +fn normalize_text(value: &str) -> String { + value + .replace('\u{200b}', " ") + .replace('\u{00a0}', " ") + .split_whitespace() + .collect::>() + .join(" ") + .trim() + .to_string() +} + +fn compact_text_blocks(blocks: &mut Vec, min_len: usize) { + let mut cleaned: Vec = Vec::new(); + + for item in blocks.drain(..) { + let normalized = normalize_text(&item); + + if normalized.len() < min_len { + continue; + } + + if cleaned.iter().any(|existing| existing == &normalized) { + continue; + } + + if cleaned + .iter() + .any(|existing| existing.len() > normalized.len() && existing.contains(&normalized)) + { + continue; + } + + cleaned.retain(|existing| { + !(normalized.len() > existing.len() && normalized.contains(existing.as_str())) + }); + cleaned.push(normalized); + } + + cleaned.sort_by(|a, b| b.len().cmp(&a.len())); + *blocks = cleaned; +} + +fn post_process_extracted(extracted: &mut HashMap>) { + if let Some(title) = extracted.get_mut("title") { + compact_text_blocks(title, 4); + if title.len() > 2 { + title.truncate(2); + } + } + + if let Some(content) = extracted.get_mut("content") { + compact_text_blocks(content, 20); + if content.len() > 5 { + content.truncate(5); + } + } +} + +fn env_truthy(name: &str) -> bool { + env::var(name) + .ok() + .map(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "y" | "on" + ) + }) + .unwrap_or(false) +} + +fn expand_tilde(path: &str) -> PathBuf { + if path == "~" { + if let Some(home) = env::var_os("HOME") { + return PathBuf::from(home); + } + } + + if let Some(rest) = path.strip_prefix("~/") { + if let Some(home) = env::var_os("HOME") { + return PathBuf::from(home).join(rest); + } + } + + PathBuf::from(path) +} + +fn default_user_data_dir() -> Option { + env::var_os("HOME") + .map(PathBuf::from) + .map(|home| home.join(".local/share/spider/login-chrome-profile")) +} + +fn resolve_output_path(output_dir: &Path, value: &str) -> PathBuf { + let path = expand_tilde(value); + if path.is_absolute() { + path + } else { + output_dir.join(path) + } +} + +fn extract_image_src(value: &str) -> Option { + let value = value.trim(); + + if let Some(rest) = value.strip_prefix('[') { + let end = rest.find(']')?; + let src = rest[..end].trim(); + if src.is_empty() { + None + } else { + Some(src.to_string()) + } + } else if value.is_empty() { + None + } else { + Some(value.to_string()) + } +} + +fn resolve_image_url(base_url: &str, raw: &str) -> Option { + if raw.starts_with("data:") || raw.starts_with("blob:") || raw.starts_with("javascript:") { + return None; + } + + if let Ok(url) = Url::parse(raw) { + return Some(url.to_string()); + } + + Url::parse(base_url) + .ok() + .and_then(|base| base.join(raw).ok()) + .map(|url| url.to_string()) +} + +fn infer_image_extension(image_url: &str, content_type: Option<&str>) -> &'static str { + if let Ok(url) = Url::parse(image_url) { + if let Some(ext) = Path::new(url.path()) + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.to_ascii_lowercase()) + { + match ext.as_str() { + "jpg" | "jpeg" => return "jpg", + "png" => return "png", + "gif" => return "gif", + "webp" => return "webp", + "svg" => return "svg", + "bmp" => return "bmp", + "avif" => return "avif", + _ => {} + } + } + } + + match content_type.unwrap_or_default() { + ct if ct.contains("image/png") => "png", + ct if ct.contains("image/gif") => "gif", + ct if ct.contains("image/webp") => "webp", + ct if ct.contains("image/svg") => "svg", + ct if ct.contains("image/bmp") => "bmp", + ct if ct.contains("image/avif") => "avif", + _ => "jpg", + } +} + +fn image_request_headers( + headers: &HeaderMap, + cookie: Option<&str>, + referer_url: &str, +) -> Result { + let mut image_headers = HeaderMap::new(); + + for key in [USER_AGENT, ACCEPT_LANGUAGE] { + if let Some(value) = headers.get(&key).cloned() { + image_headers.insert(key, value); + } + } + + image_headers.insert( + REFERER, + HeaderValue::from_str(referer_url) + .map_err(|err| Error::new(ErrorKind::InvalidInput, format!("invalid referer: {err}")))?, + ); + + if let Some(cookie) = cookie { + image_headers.insert( + COOKIE_HEADER, + HeaderValue::from_str(cookie).map_err(|err| { + Error::new(ErrorKind::InvalidInput, format!("invalid COOKIE header: {err}")) + })?, + ); + } + + Ok(image_headers) +} + +async fn download_images( + extracted: &HashMap>, + base_url: &str, + output_dir: &Path, + headers: &HeaderMap, + cookie: Option<&str>, +) -> Result { + let image_refs = match extracted.get("images") { + Some(values) => values, + None => return Ok(0), + }; + + let mut urls: Vec = Vec::new(); + for value in image_refs { + if let Some(src) = extract_image_src(value) { + if let Some(url) = resolve_image_url(base_url, &src) { + if !urls.iter().any(|existing| existing == &url) { + urls.push(url); + } + } + } + } + + if urls.is_empty() { + return Ok(0); + } + + let images_dir = output_dir.join("images"); + fs::create_dir_all(&images_dir)?; + + let image_headers = image_request_headers(headers, cookie, base_url)?; + let client = Client::builder() + .default_headers(image_headers) + .build() + .map_err(Error::other)?; + + let mut downloaded = 0usize; + + for (index, image_url) in urls.iter().enumerate() { + let response = match client.get(image_url).send().await { + Ok(response) if response.status().is_success() => response, + _ => continue, + }; + + let content_type = response + .headers() + .get(CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(str::to_string); + let bytes = match response.bytes().await { + Ok(bytes) if !bytes.is_empty() => bytes, + _ => continue, + }; + + let ext = infer_image_extension(image_url, content_type.as_deref()); + let filename = format!("{:03}.{}", index + 1, ext); + let path = images_dir.join(filename); + + if fs::write(&path, &bytes).is_ok() { + downloaded += 1; + } + } + + Ok(downloaded) +} + +fn resolve_browser_binary() -> Result { + if let Ok(path) = env::var("CHROME_BIN") { + return Ok(expand_tilde(&path)); + } + + let candidates = [ + "google-chrome", + "google-chrome-stable", + "chromium", + "chromium-browser", + "microsoft-edge", + ]; + + if let Some(path_os) = env::var_os("PATH") { + for dir in env::split_paths(&path_os) { + for name in candidates { + let candidate = dir.join(name); + if candidate.is_file() { + return Ok(candidate); + } + } + } + } + + let absolute_candidates = [ + "/usr/bin/google-chrome", + "/usr/bin/google-chrome-stable", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/snap/bin/chromium", + "/usr/bin/microsoft-edge", + ]; + + for candidate in absolute_candidates { + let path = PathBuf::from(candidate); + if path.is_file() { + return Ok(path); + } + } + + Err(Error::new( + ErrorKind::NotFound, + "failed to find Chrome/Chromium binary; set CHROME_BIN explicitly", + )) +} + +fn local_debugging_port_open(port: u16) -> bool { + let addr = SocketAddr::from(([127, 0, 0, 1], port)); + TcpStream::connect_timeout(&addr, Duration::from_millis(250)).is_ok() +} + +fn wait_for_local_browser(port: u16, timeout: Duration) -> Result<()> { + let addr = SocketAddr::from(([127, 0, 0, 1], port)); + let started = std::time::Instant::now(); + + while started.elapsed() < timeout { + if TcpStream::connect_timeout(&addr, Duration::from_millis(250)).is_ok() { + return Ok(()); + } + thread::sleep(Duration::from_millis(250)); + } + + Err(Error::new( + ErrorKind::TimedOut, + format!("timed out waiting for local browser debug port {port}"), + )) +} + +fn local_connection_url(port: u16) -> String { + format!("http://127.0.0.1:{port}/json/version") +} + +fn parse_extra_args() -> Vec { + env::var("CHROME_EXTRA_ARGS") + .ok() + .map(|value| value.split_whitespace().map(ToOwned::to_owned).collect()) + .unwrap_or_default() +} + +fn apply_proxy_environment(command: &mut Command) { + if env_truthy("CHROME_INHERIT_PROXY") { + return; + } + + for key in [ + "http_proxy", + "https_proxy", + "all_proxy", + "no_proxy", + "HTTP_PROXY", + "HTTPS_PROXY", + "ALL_PROXY", + "NO_PROXY", + ] { + command.env_remove(key); + } +} + +fn launch_local_browser( + browser_binary: &Path, + user_data_dir: &Path, + profile_dir: &str, + port: u16, + start_url: &str, + headless: bool, + extra_args: &[String], +) -> Result { + fs::create_dir_all(user_data_dir)?; + + let mut command = Command::new(browser_binary); + apply_proxy_environment(&mut command); + command + .arg(format!("--remote-debugging-port={port}")) + .arg("--remote-debugging-address=127.0.0.1") + .arg("--no-first-run") + .arg("--no-default-browser-check") + .arg(format!("--user-data-dir={}", user_data_dir.display())) + .arg(format!("--profile-directory={profile_dir}")); + + if let Ok(proxy) = env::var("CHROME_PROXY") { + if !proxy.trim().is_empty() { + command.arg(format!("--proxy-server={}", proxy.trim())); + } + } else { + command.arg("--no-proxy-server"); + } + + if headless { + command.arg("--headless=new"); + } + + if !extra_args.is_empty() { + command.args(extra_args); + } + + command.arg(start_url).stdin(Stdio::null()); + + command.spawn() +} + +fn prepare_local_chrome( + user_data_dir: PathBuf, + profile_dir: String, + port: u16, + start_url: String, + headless: bool, + extra_args: &[String], +) -> Result<(String, Option, PathBuf, String)> { + if local_debugging_port_open(port) { + return Ok(( + local_connection_url(port), + None, + user_data_dir, + profile_dir, + )); + } + + let browser_binary = resolve_browser_binary()?; + let child = launch_local_browser( + &browser_binary, + &user_data_dir, + &profile_dir, + port, + &start_url, + headless, + extra_args, + )?; + + wait_for_local_browser(port, Duration::from_secs(20))?; + + Ok(( + local_connection_url(port), + Some(child), + user_data_dir, + profile_dir, + )) +} + +#[tokio::main] +async fn main() -> Result<()> { + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + env_logger::init_from_env(env); + + let cookie = env::var("COOKIE").ok(); + let prepare_profile = env_truthy("PREPARE_PROFILE"); + let chrome_headless = env_truthy("CHROME_HEADLESS"); + let chrome_profile_dir = + env::var("CHROME_PROFILE_DIR").unwrap_or_else(|_| DEFAULT_PROFILE_DIR.to_string()); + let chrome_debugging_port = env::var("CHROME_DEBUGGING_PORT") + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(DEFAULT_DEBUGGING_PORT); + let chrome_start_url = + env::var("CHROME_START_URL").unwrap_or_else(|_| DEFAULT_START_URL.to_string()); + let chrome_extra_args = parse_extra_args(); + let mut chrome_connection_url = env::var("CHROME_CONNECTION_URL").ok(); + let target = env::args() + .nth(1) + .or_else(|| env::var("TARGET_URL").ok()) + .unwrap_or_else(|| DEFAULT_TARGET_URL.to_string()); + let output_dir = env::var("OUTPUT_DIR").unwrap_or_else(|_| DEFAULT_OUTPUT_DIR.to_string()); + let output_dir = expand_tilde(&output_dir); + fs::create_dir_all(&output_dir)?; + let output_html = env::var("OUTPUT_HTML").unwrap_or_else(|_| "page.html".to_string()); + let output_json = + env::var("OUTPUT_JSON").unwrap_or_else(|_| "page_extracted.json".to_string()); + let output_html_path = resolve_output_path(&output_dir, &output_html); + let output_json_path = resolve_output_path(&output_dir, &output_json); + let wait_selector = + env::var("WAIT_FOR_SELECTOR").unwrap_or_else(|_| DEFAULT_WAIT_SELECTOR.to_string()); + let download_images_enabled = !matches!( + env::var("DOWNLOAD_IMAGES").ok().as_deref(), + Some("0" | "false" | "False" | "FALSE") + ); + + let should_use_local_profile = + chrome_connection_url.is_none() && (prepare_profile || cookie.is_none()); + + let mut local_profile_summary: Option<(PathBuf, String)> = None; + let mut _launched_browser: Option = None; + + if should_use_local_profile { + let user_data_dir = env::var("CHROME_USER_DATA_DIR") + .ok() + .map(|value| expand_tilde(&value)) + .or_else(default_user_data_dir) + .ok_or_else(|| Error::other("failed to resolve CHROME_USER_DATA_DIR"))?; + + let (connection_url, child, user_data_dir, profile_dir) = prepare_local_chrome( + user_data_dir, + chrome_profile_dir.clone(), + chrome_debugging_port, + chrome_start_url.clone(), + chrome_headless, + &chrome_extra_args, + )?; + + chrome_connection_url = Some(connection_url); + local_profile_summary = Some((user_data_dir, profile_dir)); + _launched_browser = child; + } + + if prepare_profile { + let (user_data_dir, profile_dir) = local_profile_summary.ok_or_else(|| { + Error::other("PREPARE_PROFILE requires local browser profile preparation") + })?; + + println!("Prepared local browser profile for authenticated browsing."); + println!("User data dir: {}", user_data_dir.display()); + println!("Profile dir: {}", profile_dir); + println!( + "CDP endpoint: {}", + chrome_connection_url + .as_deref() + .unwrap_or("http://127.0.0.1:9222/json/version") + ); + println!( + "Open the browser window, log in to the target site, then rerun without PREPARE_PROFILE=1." + ); + println!("If you do not see a window, unset CHROME_HEADLESS or set it to 0."); + return Ok(()); + } + + let mut headers = HeaderMap::new(); + let user_agent = env::var("USER_AGENT").unwrap_or_else(|_| { + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36".to_string() + }); + let accept_language = env::var("ACCEPT_LANGUAGE_HEADER") + .unwrap_or_else(|_| "zh-CN,zh;q=0.9,en;q=0.8".to_string()); + headers.insert( + USER_AGENT, + HeaderValue::from_str(&user_agent) + .map_err(|err| Error::new(ErrorKind::InvalidInput, format!("invalid USER_AGENT: {err}")))?, + ); + headers.insert( + ACCEPT_LANGUAGE, + HeaderValue::from_str(&accept_language).map_err(|err| { + Error::new( + ErrorKind::InvalidInput, + format!("invalid ACCEPT_LANGUAGE_HEADER: {err}"), + ) + })?, + ); + if let Ok(referer) = env::var("REFERER_URL") { + headers.insert( + REFERER, + HeaderValue::from_str(&referer).map_err(|err| { + Error::new(ErrorKind::InvalidInput, format!("invalid REFERER_URL: {err}")) + })?, + ); + } + let headers_for_download = headers.clone(); + + let mut website: Website = Website::new(&target); + website + .with_limit(1) + .with_headers(Some(headers)) + .with_wait_for_idle_network0(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(5))))) + .with_wait_for_selector(Some(WaitForSelector::new( + Some(Duration::from_secs(20)), + wait_selector.clone().into(), + ))) + .with_wait_for_idle_dom(Some(WaitForSelector::new( + Some(Duration::from_millis(800)), + wait_selector.into(), + ))) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true); + + if let Some(cookie) = cookie.as_deref() { + website.with_cookies(cookie); + } + + if let Some(ref chrome_connection_url) = chrome_connection_url { + website.with_chrome_connection(Some(chrome_connection_url.clone())); + } + + let mut website = website + .build() + .map_err(|_| Error::other("failed to build website config"))?; + + let mut rx = website + .subscribe(16) + .ok_or_else(|| Error::other("failed to subscribe to page stream"))?; + + let collector = tokio::spawn(async move { + let mut pages: Vec = Vec::new(); + + while let Ok(page) = rx.recv().await { + pages.push(page); + } + + pages + }); + + website.crawl().await; + website.unsubscribe(); + + let pages = collector + .await + .map_err(|err| Error::other(format!("collector task failed: {err}")))?; + + let page = pages + .first() + .ok_or_else(|| Error::new(ErrorKind::NotFound, "no page was captured"))?; + + let html = page.get_html(); + std::fs::write(&output_html_path, &html)?; + + let mut extracted = + spider_utils::css_query_select_map_streamed(&html, &extraction_selectors()).await; + post_process_extracted(&mut extracted); + std::fs::write( + &output_json_path, + serde_json::to_vec_pretty(&extracted).map_err(json_error)?, + )?; + + let downloaded_images = if download_images_enabled { + download_images( + &extracted, + page.get_url_final(), + &output_dir, + &headers_for_download, + cookie.as_deref(), + ) + .await? + } else { + 0 + }; + + println!("Captured URL: {}", page.get_url()); + println!("Final URL: {}", page.get_url_final()); + println!("HTML bytes: {}", page.get_html_bytes_u8().len()); + if let Some((user_data_dir, profile_dir)) = local_profile_summary { + println!( + "Local profile reused: {} [{}]", + user_data_dir.display(), + profile_dir + ); + } else if let Some(chrome_connection_url) = chrome_connection_url.as_deref() { + println!("Chrome connection reused: {}", chrome_connection_url); + } else if cookie.is_some() { + println!("Authentication mode: cookie injection"); + } + println!("Saved artifacts to dir: {}", output_dir.display()); + println!("Saved HTML to: {}", output_html_path.display()); + println!("Saved extracted content to: {}", output_json_path.display()); + if download_images_enabled { + println!("Downloaded images: {}", downloaded_images); + println!("Images dir: {}", output_dir.join("images").display()); + } + println!( + "{}", + serde_json::to_string_pretty(&extracted).map_err(json_error)? + ); + + Ok(()) +} diff --git a/examples/auto_encoding.rs b/examples/auto_encoding.rs new file mode 100644 index 000000000..0db39c84b --- /dev/null +++ b/examples/auto_encoding.rs @@ -0,0 +1,25 @@ +//! `cargo run --example auto_encoding --features encoding` +extern crate spider; + +use spider::hashbrown::HashMap; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = + Website::new("https://hoken.kakaku.com/health_check/blood_pressure/") + .with_budget(Some(HashMap::from([("*", 2)]))) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + println!("{:?}", res.get_html()); + } + }); + + website.crawl().await; +} diff --git a/examples/blacklist.rs b/examples/blacklist.rs new file mode 100644 index 000000000..2f0418209 --- /dev/null +++ b/examples/blacklist.rs @@ -0,0 +1,43 @@ +//! `cargo run --example blacklist --features regex` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en/"); + + website.with_blacklist_url(Some(vec!["/books".into(), "/blog".into(), "/tag".into()])); + + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + let mut stdout = tokio::io::stdout(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + stdout + }); + + let start = std::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let duration = start.elapsed(); + let mut stdout = join_handle.await.unwrap(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_links().len() + ) + .as_bytes(), + ) + .await; +} diff --git a/examples/budget.rs b/examples/budget.rs new file mode 100644 index 000000000..eddf012d7 --- /dev/null +++ b/examples/budget.rs @@ -0,0 +1,38 @@ +//! `cargo run --example budget --features budget` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::io::Error; +use std::time::Instant; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let mut website = Website::new("https://rsseau.fr/en") + .with_budget(Some(spider::hashbrown::HashMap::from([ + ("*", 15), + ("en", 11), + ("fr", 3), + ]))) + .with_limit(15) // this does the same as the above "*", 15. Setting a limit to the amount of pages gathered. + .build() + .unwrap(); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ); + + Ok(()) +} diff --git a/examples/cache.rs b/examples/cache.rs new file mode 100644 index 000000000..217fc33f7 --- /dev/null +++ b/examples/cache.rs @@ -0,0 +1,74 @@ +//! `cargo run --example cache --features="spider/sync spider/cache"` +extern crate spider; + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use crate::spider::http_cache_reqwest::CacheManager; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::string_concat::{string_concat, string_concat_impl}; +use spider::tokio; +use spider::website::Website; + +static GLOBAL_URL_COUNT: AtomicUsize = AtomicUsize::new(0); + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en") + .with_caching(true) + .build() + .unwrap(); + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(16).unwrap(); + + let start = std::time::Instant::now(); + + let mut website1 = website.clone(); + let mut website2 = website.clone(); + + let subscription = async move { + while let Ok(res) = rx2.recv().await { + let mut stdout = tokio::io::stdout(); + let cache_url = string_concat!("GET:", res.get_url()); + + tokio::task::spawn(async move { + let result = tokio::time::timeout(Duration::from_millis(60), async { + spider::website::CACACHE_MANAGER.get(&cache_url).await + }) + .await; + + match result { + Ok(Ok(Some(_cache))) => { + let message = format!("HIT - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + Ok(Ok(None)) | Ok(Err(_)) => { + let message = format!("MISS - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + Err(_) => { + let message = format!("ERROR - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + }; + + GLOBAL_URL_COUNT.fetch_add(1, Ordering::Relaxed); + }); + } + }; + + tokio::pin!(subscription); + + tokio::select! { + _ = website1.crawl() => (), + _ = website2.crawl() => (), + _ = subscription => (), + }; + + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, GLOBAL_URL_COUNT + ) +} diff --git a/examples/cache_chrome_hybrid.rs b/examples/cache_chrome_hybrid.rs new file mode 100644 index 000000000..39db9e45f --- /dev/null +++ b/examples/cache_chrome_hybrid.rs @@ -0,0 +1,100 @@ +//! cargo run --example cache_chrome_hybrid --features="spider/sync spider/chrome spider/cache_chrome_hybrid" +extern crate spider; + +use crate::spider::http_cache_reqwest::CacheManager; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::string_concat::{string_concat, string_concat_impl}; +use spider::tokio; +use spider::website::Website; +use spider::{ + configuration::ChromeEventTracker, features::chrome_common::RequestInterceptConfiguration, +}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +static GLOBAL_URL_COUNT: AtomicUsize = AtomicUsize::new(0); + +#[tokio::main] +async fn main() { + let mut interception = RequestInterceptConfiguration::new(true); + let mut tracker = ChromeEventTracker::default(); + + interception.block_javascript = true; + + tracker.responses = true; + tracker.requests = true; + + let mut website: Website = Website::new("https://spider.cloud") + .with_caching(true) + .with_chrome_intercept(interception) + .with_event_tracker(Some(tracker)) + .build() + .unwrap(); + let mut rx2 = website.subscribe(500).unwrap(); + + let start = std::time::Instant::now(); + + tokio::spawn(async move { + let mut stdout = tokio::io::stdout(); + + while let Ok(res) = rx2.recv().await { + let message = format!("CACHING - {:?}\n", res.get_url()); + let _ = stdout.write_all(message.as_bytes()).await; + } + }); + + website.crawl().await; + website.unsubscribe(); + + let mut rx2 = website.subscribe(500).unwrap(); + + let subscription = async move { + while let Ok(res) = rx2.recv().await { + let mut stdout = tokio::io::stdout(); + let cache_url = string_concat!("GET:", res.get_url()); + + tokio::task::spawn(async move { + let result = tokio::time::timeout(Duration::from_millis(60), async { + spider::website::CACACHE_MANAGER.get(&cache_url).await + }) + .await; + + match result { + Ok(Ok(Some(_cache))) => { + let message = format!("HIT - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + Ok(Ok(None)) | Ok(Err(_)) => { + let message = format!("MISS - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + Err(_) => { + let message = format!("ERROR - {:?}\n", cache_url); + let _ = stdout.write_all(message.as_bytes()).await; + } + }; + + GLOBAL_URL_COUNT.fetch_add(1, Ordering::Relaxed); + }); + } + }; + + let crawl = async move { + website.crawl_raw().await; + website.unsubscribe(); + }; + + tokio::pin!(subscription); + + tokio::select! { + _ = crawl => (), + _ = subscription => (), + }; + + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, GLOBAL_URL_COUNT + ) +} diff --git a/examples/cache_remote_skip_browser.rs b/examples/cache_remote_skip_browser.rs new file mode 100644 index 000000000..4aecc2203 --- /dev/null +++ b/examples/cache_remote_skip_browser.rs @@ -0,0 +1,67 @@ +//! End-to-end remote cache demo (index_cache_server / hybrid_cache_server). +//! +//! 1) Start your remote cache server (default expected at http://127.0.0.1:8080) +//! 2) Set `HYBRID_CACHE_ENDPOINT` if different. +//! 3) Run: +//! cargo run --example cache_remote_skip_browser --features="spider/sync spider/chrome spider/chrome_remote_cache" -- https://example.com/ + +extern crate spider; + +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; + +async fn crawl_once(url: &str, skip_browser: bool) -> (std::time::Duration, Option, usize) { + let mut website: Website = Website::new(url) + .with_limit(1) + .with_caching(true) + .with_cache_skip_browser(skip_browser) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .build() + .expect("build website"); + + let mut rx = website.subscribe(4).expect("subscribe"); + let page_task = tokio::spawn(async move { rx.recv().await.ok() }); + + let started = tokio::time::Instant::now(); + website.crawl_raw().await; + let elapsed = started.elapsed(); + website.unsubscribe(); + + let page = page_task.await.ok().and_then(|p| p); + + let bytes_transferred = page.as_ref().and_then(|p| p.bytes_transferred); + let html_len = page.map_or(0, |p| p.get_html_bytes_u8().len()); + + (elapsed, bytes_transferred, html_len) +} + +#[tokio::main] +async fn main() { + env_logger::init(); + + let target = std::env::args() + .nth(1) + .unwrap_or_else(|| "https://example.com/".to_string()); + let endpoint = std::env::var("HYBRID_CACHE_ENDPOINT") + .unwrap_or_else(|_| "http://127.0.0.1:8080".to_string()); + + println!("target={target}"); + println!("hybrid_cache_endpoint={endpoint}"); + + // Pass 1: warm cache (browser can run as usual) + let (warm_duration, warm_bytes, warm_html_len) = crawl_once(&target, false).await; + println!( + "warm_pass: duration={warm_duration:?}, bytes_transferred={warm_bytes:?}, html_len={warm_html_len}" + ); + + // Pass 2: enable skip-browser mode, return cached value immediately if available + let (cached_duration, cached_bytes, cached_html_len) = crawl_once(&target, true).await; + println!( + "cached_pass(skip_browser): duration={cached_duration:?}, bytes_transferred={cached_bytes:?}, html_len={cached_html_len}" + ); + + println!( + "cache_hit_hint: bytes_transferred is commonly None on direct cached returns, and duration usually drops significantly." + ); +} diff --git a/examples/callback.rs b/examples/callback.rs new file mode 100644 index 000000000..18f724285 --- /dev/null +++ b/examples/callback.rs @@ -0,0 +1,20 @@ +//! `cargo run --example callback` +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + + let some = "custom"; + + website.set_on_link_find(move |s, ss| { + println!("link target: {:?} - {some}", s); + // forward link to a different destination + (s.as_ref().replacen("/fr/", "", 1).into(), ss) + }); + + website.crawl().await; +} diff --git a/examples/chrome.rs b/examples/chrome.rs new file mode 100644 index 000000000..f2f3f2920 --- /dev/null +++ b/examples/chrome.rs @@ -0,0 +1,52 @@ +//! cargo run --example chrome --features chrome + +extern crate spider; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::io::Result; + +async fn crawl_website(url: &str) -> Result<()> { + let mut website: Website = Website::new(url) + .with_limit(10) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + + let handle = tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{:?}", page.get_url()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let _ = handle.await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl({url}) is: {:?} for total pages: {:?}", + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://github.com/spider-rs"), + ); + + Ok(()) +} diff --git a/examples/chrome_remote.rs b/examples/chrome_remote.rs new file mode 100644 index 000000000..55eb4ce63 --- /dev/null +++ b/examples/chrome_remote.rs @@ -0,0 +1,71 @@ +//! cargo run --example chrome_remote --features="chrome chrome_intercept" + +// May 11th, 2025: Highly recommend you use the headless-browser project ( https://github.com/spider-rs/headless-browser/tree/main ) for remote Chrome. +// See https://github.com/spider-rs/spider/issues/294#issuecomment-2869870886 + +extern crate spider; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::io::Result; + +async fn crawl_website(url: &str) -> Result<()> { + let mut website: Website = Website::new(url) + .with_limit(500) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .with_fingerprint(true) + // .with_proxies(Some(vec!["http://localhost:8888".into()])) + .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + let mut stdout = tokio::io::stdout(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "- {} -- Bytes transferred {:?} -- HTML Size {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len() + ) + .as_bytes(), + ) + .await; + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + website.get_url(), + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://example.com"), + ); + + Ok(()) +} diff --git a/examples/chrome_remote_tls.rs b/examples/chrome_remote_tls.rs new file mode 100644 index 000000000..3b8e9e7c5 --- /dev/null +++ b/examples/chrome_remote_tls.rs @@ -0,0 +1,68 @@ +//! cargo run --example chrome_remote_tls --features="chrome chrome_intercept chrome_tls_connection" + +extern crate spider; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::io::Result; + +async fn crawl_website(url: &str) -> Result<()> { + let mut website: Website = Website::new(url) + .with_limit(500) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .with_fingerprint(true) + // .with_proxies(Some(vec!["http://localhost:8888".into()])) + .with_chrome_connection(Some( + "wss://replace_this_with_your_connection_string".into(), + )) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + let mut stdout = tokio::io::stdout(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "- {} -- Bytes transferred {:?} -- HTML Size {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len() + ) + .as_bytes(), + ) + .await; + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + website.get_url(), + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://example.com"), + ); + + Ok(()) +} diff --git a/examples/chrome_screenshot.rs b/examples/chrome_screenshot.rs new file mode 100644 index 000000000..f926303fa --- /dev/null +++ b/examples/chrome_screenshot.rs @@ -0,0 +1,49 @@ +//! Make sure to create a storage directory locally. +//! cargo run --example chrome_screenshot --features="spider/sync spider/chrome spider/chrome_store_page" +extern crate spider; +use std::path::PathBuf; + +use spider::tokio; +use spider::utils::create_output_path; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.configuration.request_timeout = Some(std::time::Duration::from_secs(60).into()); + let mut rx2 = website.subscribe(18).unwrap(); + let mut rxg = website.subscribe_guard().unwrap(); + + tokio::spawn(async move { + while let Ok(mut page) = rx2.recv().await { + let file_format = spider::configuration::CaptureScreenshotFormat::Png; + let output_path = + create_output_path(&PathBuf::from("./storage/"), page.get_url(), ".png").await; + + let bytes = page + .screenshot(true, true, file_format, Some(75), Some(output_path), None) + .await; + + println!( + "{} - {:?}", + if bytes.is_empty() { "🚫" } else { "📸" }, + page.get_url() + ); + + page.close_page().await; + rxg.inc(); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/chrome_screenshot_with_config.rs b/examples/chrome_screenshot_with_config.rs new file mode 100644 index 000000000..52b955520 --- /dev/null +++ b/examples/chrome_screenshot_with_config.rs @@ -0,0 +1,42 @@ +//! Make sure to create a storage directory locally. +//! cargo run --example chrome_screenshot_with_config --features="spider/sync spider/chrome" +extern crate spider; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + // the cdp params + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(true)); + // params that handle the way to take screenshots + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let mut website: Website = Website::new("https://choosealicense.com") + .with_screenshot(Some(screenshot_config)) + .build() + .unwrap(); + let mut rx2 = website.subscribe(18).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + if page.screenshot_bytes.is_none() { + println!("🚫 - {:?}", page.get_url()); + } else { + println!("📸 - {:?}", page.get_url()); + } + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/chrome_sendable.rs b/examples/chrome_sendable.rs new file mode 100644 index 000000000..20c05d027 --- /dev/null +++ b/examples/chrome_sendable.rs @@ -0,0 +1,67 @@ +//! cargo run --example chrome_sendable --features="chrome chrome_intercept" + +extern crate spider; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::io::Result; + +async fn crawl_website(website: &Website, url: &str) -> Result<()> { + let start = crate::tokio::time::Instant::now(); + + website.crawl_chrome_send(Some(url)).await; + + println!( + "Time elapsed in website.crawl({}) is: {:?}", + url, + start.elapsed(), + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + let mut stdout = tokio::io::stdout(); + let mut website = Website::default(); + + website + .with_limit(5) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .with_fingerprint(true) + .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())); + + website.configure_setup().await; + + let mut rx2 = website.subscribe(16).unwrap(); + + let handle = tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "- {} -- Bytes transferred {:?} -- HTML Size {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len() + ) + .as_bytes(), + ) + .await; + } + }); + + let _ = tokio::join!( + crawl_website(&website, "https://choosealicense.com"), + crawl_website(&website, "https://jeffmendez.com"), + crawl_website(&website, "https://example.com"), + ); + + drop(website); + + let _ = handle.await; + + Ok(()) +} diff --git a/examples/chrome_viewport.rs b/examples/chrome_viewport.rs new file mode 100644 index 000000000..fee706727 --- /dev/null +++ b/examples/chrome_viewport.rs @@ -0,0 +1,37 @@ +//! `cargo run --example chrome_viewport --features chrome` +extern crate spider; + +use spider::configuration::Viewport; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://choosealicense.com") + .with_viewport(Some(Viewport::new(375, 500))) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{:?}", page.get_url()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/chrome_web_automation.rs b/examples/chrome_web_automation.rs new file mode 100644 index 000000000..578238e18 --- /dev/null +++ b/examples/chrome_web_automation.rs @@ -0,0 +1,65 @@ +//! `cargo run --example chrome_web_automation --features chrome chrome_headed` +extern crate spider; + +use std::time::Duration; + +use spider::configuration::{WaitForIdleNetwork, WebAutomation}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::hashbrown::HashMap; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut automation_scripts = HashMap::new(); + + automation_scripts.insert( + "/en/blog".into(), + Vec::from([ + WebAutomation::Evaluate(r#"document.body.style.background = "blue";"#.into()), + WebAutomation::ScrollY(2000), + WebAutomation::Click("article a".into()), + WebAutomation::Wait(5000), + WebAutomation::Screenshot { + output: "example.png".into(), + full_page: true, + omit_background: true, + }, + ]), + ); + let mut tracker = spider::configuration::ChromeEventTracker::new(true, true); + tracker.automation = true; + + let mut website: Website = Website::new("https://rsseau.fr/en/blog") + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_limit(1) + .with_event_tracker(Some(tracker)) + .with_automation_scripts(Some(automation_scripts)) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{:?} - {:?}", page.get_url(), page.get_metadata()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/concurrent_profiles.rs b/examples/concurrent_profiles.rs new file mode 100644 index 000000000..868033f45 --- /dev/null +++ b/examples/concurrent_profiles.rs @@ -0,0 +1,119 @@ +//! cargo run --example concurrent_profiles --features="chrome chrome_intercept disk" + +extern crate spider; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::configuration::Fingerprint; +use spider::features::chrome_common::RequestInterceptConfiguration; + +use spider::tokio; +use spider::utils::split_hashset_round_robin; +use spider::website::Website; +use std::io::Result; + +const CRAWL_LIMIT: u32 = 50; + +async fn crawl_website(url: &str) -> Result<()> { + let mut website: Website = Website::new(url) + .with_limit(CRAWL_LIMIT) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_stealth(true) + .with_shared_state(true) + .with_sqlite(true) + .with_fingerprint(true) + .with_proxies(Some(vec!["http://localhost:9111".into()])) + .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + + let shared_db = website.setup_database_handler(); + + let pool = shared_db.generate_pool().await; + + shared_db.set_pool(pool).await; + website.setup_shared_db(shared_db); + + website.set_disk_persistance(true); + + let mut rx2 = website.subscribe(16).unwrap(); + let mut website1 = website.clone(); + + let website1 = website1 + .with_proxies(Some(vec!["http://localhost:9112".into()])) + .with_fingerprint_advanced(Fingerprint::Basic); + + let mut stdout = tokio::io::stdout(); + + tokio::spawn(async move { + let mut total_links = 0; + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "- {} -- Bytes transferred {:?} -- HTML Size {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len() + ) + .as_bytes(), + ) + .await; + total_links += 1; + } + println!("Total links: {:?}", total_links); + }); + + let start = crate::tokio::time::Instant::now(); + + website.crawl_raw().await; + + website.persist_links(); + website1.persist_links(); + + let extra_links = website.drain_extra_links(); + let extra_links: spider::hashbrown::HashSet = + extra_links.collect(); + + let mut set: Vec> = + split_hashset_round_robin(extra_links, 2); + + website1.set_extra_links(set.remove(0)); + website.set_extra_links(set.remove(0)); + + website1.with_limit(CRAWL_LIMIT * 2); + website.with_limit(CRAWL_LIMIT * 2); + + website.set_disk_persistance(false); + website1.set_disk_persistance(false); + + println!("STARTING CONCURRENT RUN HTTP-CHROME"); + + tokio::join!(website.crawl_raw(), website1.crawl()); + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + website.get_url(), + duration, + links.len() + ); + + website.clear_all().await; + website1.clear_all().await; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://spider.cloud"), + ); + + Ok(()) +} diff --git a/examples/configuration.rs b/examples/configuration.rs new file mode 100644 index 000000000..eb42c3b2b --- /dev/null +++ b/examples/configuration.rs @@ -0,0 +1,41 @@ +//! `cargo run --example configuration` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::io::Error; +use std::time::Instant; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let mut website = Website::new("https://rsseau.fr/en") + .with_user_agent(Some("SpiderBot")) + .with_blacklist_url(Some(Vec::from(["https://rsseau.fr/resume".into()]))) + .with_subdomains(true) + .with_concurrency_limit(None) + .with_tld(true) + .with_respect_robots_txt(true) + .with_external_domains(Some( + Vec::from(["http://loto.rsseau.fr/"].map(|d| d.to_string())).into_iter(), + )) + .build() + .unwrap(); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ); + + Ok(()) +} diff --git a/examples/cron.rs b/examples/cron.rs new file mode 100644 index 000000000..ee16ca46b --- /dev/null +++ b/examples/cron.rs @@ -0,0 +1,25 @@ +//! `cargo run --example cron` +extern crate spider; + +use spider::tokio; +use spider::website::{run_cron, Website}; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + website.configuration.cron_str = "1/5 * * * * *".into(); + + let mut rx2 = website.subscribe(16).unwrap(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + } + }); + + let mut runner = run_cron(website).await; + + println!("Starting the Runner for 20 seconds"); + tokio::time::sleep(tokio::time::Duration::from_secs(20)).await; + let _ = tokio::join!(runner.stop(), join_handle); +} diff --git a/examples/css_scrape.rs b/examples/css_scrape.rs new file mode 100644 index 000000000..3454a45ea --- /dev/null +++ b/examples/css_scrape.rs @@ -0,0 +1,47 @@ +//! `cargo run --example css_scrape` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use spider_utils::{ + build_selectors, css_query_select_map_streamed, QueryCSSMap, QueryCSSSelectSet, +}; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://spider.cloud") + .with_limit(5) + .build() + .unwrap(); + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + let mut stdout = tokio::io::stdout(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let map = QueryCSSMap::from([("list", QueryCSSSelectSet::from(["ul", "ol"]))]); + let data = css_query_select_map_streamed(&res.get_html(), &build_selectors(map)).await; + + let _ = stdout.write_all(format!("- {:?}\n", data).as_bytes()).await; + } + stdout + }); + + let start = std::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let duration = start.elapsed(); + let mut stdout = join_handle.await.unwrap(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_size().await + ) + .as_bytes(), + ) + .await; +} diff --git a/examples/debug.rs b/examples/debug.rs new file mode 100644 index 000000000..f630555ef --- /dev/null +++ b/examples/debug.rs @@ -0,0 +1,22 @@ +//! `cargo run --example debug` +//! example to demonstate enabling logging within the crate +extern crate env_logger; +extern crate spider; + +use env_logger::Env; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + // enable with RUST_LOG env_logger crate + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + + let mut website: Website = Website::new("https://rsseau.fr/en"); + + website.crawl().await; +} diff --git a/examples/depth.rs b/examples/depth.rs new file mode 100644 index 000000000..3c8590b30 --- /dev/null +++ b/examples/depth.rs @@ -0,0 +1,33 @@ +//! `cargo run --example depth` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::io::Error; +use std::time::Instant; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let mut website = Website::new("https://rsseau.fr/en") + .with_depth(3) + .build() + .unwrap(); + + let start = Instant::now(); + website.crawl().await; + let duration: std::time::Duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ); + + Ok(()) +} diff --git a/examples/download.rs b/examples/download.rs new file mode 100644 index 000000000..2f432fd4d --- /dev/null +++ b/examples/download.rs @@ -0,0 +1,55 @@ +//! `cargo run --example download` +extern crate env_logger; +extern crate spider; + +use spider::percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use spider::utils::log; +use spider::website::Website; + +use env_logger::Env; +use spider::tokio; +use std::fs::OpenOptions; +use std::io::Write; + +#[tokio::main] +async fn main() { + // view the target dist for the downloads + std::fs::create_dir_all("./target/downloads").unwrap_or_default(); + + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + + let website_name = "https://spider.cloud"; + + let mut website: Website = Website::new(website_name); + + website.scrape().await; + + for page in website.get_pages().unwrap().iter() { + let download_file = percent_encode(page.get_url().as_bytes(), NON_ALPHANUMERIC).to_string(); + + let download_file = if download_file.is_empty() { + "index" + } else { + &download_file + }; + + let download_file = format!("./target/downloads/{}.html", download_file); + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&download_file) + .expect("Unable to open file"); + + if let Some(b) = page.get_bytes() { + file.write_all(b).unwrap_or_default(); + } + + log("downloaded", download_file) + } +} diff --git a/examples/download_to_react.rs b/examples/download_to_react.rs new file mode 100644 index 000000000..4cc37e3ed --- /dev/null +++ b/examples/download_to_react.rs @@ -0,0 +1,69 @@ +//! `cargo run --example download_to_react` +extern crate convert_case; +extern crate env_logger; +extern crate htr; +extern crate spider; + +use spider::percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use spider::utils::log; +use spider::website::Website; + +use convert_case::{Case, Casing}; +use env_logger::Env; +use htr::convert_to_react; +use spider::tokio; +use std::env; +use std::fs::OpenOptions; +use std::io::Write; + +#[tokio::main] +async fn main() { + let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "./target".to_string()); + + // view the target dist for the downloads + std::fs::create_dir_all(format!("{}/downloads", target_dir)).unwrap_or_default(); + + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + + let website_name = "https://rsseau.fr/en"; + + let mut website: Website = Website::new(website_name); + website.configuration.respect_robots_txt = true; + website.configuration.delay = 0; + + website.scrape().await; + + if let Some(pages) = website.get_pages() { + for page in pages.iter() { + let download_file = + percent_encode(page.get_url().as_bytes(), NON_ALPHANUMERIC).to_string(); + + let download_file = if download_file.is_empty() { + "index" + } else { + &download_file + }; + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(format!("{}/downloads/{}.tsx", target_dir, download_file)) + .expect("Unable to open file"); + + let download_file = download_file.to_case(Case::Camel); + let download_file = download_file[0..1].to_uppercase() + &download_file[1..]; + + let react_component = convert_to_react(&page.get_html(), download_file.to_string()); + let react_component = react_component.as_bytes(); + + file.write_all(react_component).unwrap_or_default(); + + log("downloaded", download_file); + } + }; +} diff --git a/examples/encoding.rs b/examples/encoding.rs new file mode 100644 index 000000000..385527c72 --- /dev/null +++ b/examples/encoding.rs @@ -0,0 +1,25 @@ +//! `cargo run --example encoding --features encoding` +extern crate spider; + +use spider::hashbrown::HashMap; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = + Website::new("https://hoken.kakaku.com/health_check/blood_pressure/") + .with_budget(Some(HashMap::from([("*", 2)]))) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + println!("{:?}", res.get_html_encoded("SHIFT_JIS")); + } + }); + + website.crawl().await; +} diff --git a/examples/example.rs b/examples/example.rs new file mode 100644 index 000000000..c41669970 --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,36 @@ +//! `cargo run --example example` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::time::Instant; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + website + .configuration + .blacklist_url + .insert(Default::default()) + .push("https://rsseau.fr/resume".into()); + website.configuration.respect_robots_txt = true; + website.configuration.subdomains = false; + website.configuration.delay = 0; // Defaults to 0 ms + website.configuration.user_agent = Some(Box::new("SpiderBot".into())); // Defaults to spider/x.y.z, where x.y.z is the library version + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/gemini.rs b/examples/gemini.rs new file mode 100644 index 000000000..c65131756 --- /dev/null +++ b/examples/gemini.rs @@ -0,0 +1,78 @@ +//! `GEMINI_API_KEY=$MYAPI_KEY cargo run --example gemini --features "spider/sync spider/gemini"` +extern crate spider; + +use std::time::Duration; + +use spider::configuration::{GeminiConfigs, WaitForIdleNetwork}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::hashbrown::{HashMap, HashSet}; +use spider::website::Website; +use spider::{tokio, CaseInsensitiveString}; + +#[tokio::main] +async fn main() { + let _ = tokio::fs::create_dir_all("./storage/").await; + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let website_url = "https://www.google.com"; + + let mut gemini_config = GeminiConfigs::default(); + let prompt_url_map = HashMap::from([ + ( + CaseInsensitiveString::new(website_url), + Box::new(GeminiConfigs::new( + "gemini-flash-latest", + "Search for Movies", + 500, + )), + ), + ( + CaseInsensitiveString::new( + &((website_url.to_owned()) + "/search/howsearchworks/?fg=1"), + ), + Box::new(GeminiConfigs::new( + "gemini-flash-latest", + "Change the background blue", + 500, + )), + ), + ]); + + gemini_config.prompt_url_map = Some(Box::new(prompt_url_map)); + + let mut website: Website = Website::new(website_url) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_screenshot(Some(screenshot_config)) + .with_limit(2) + .with_stealth(true) + .with_fingerprint(true) + .with_gemini(Some(gemini_config)) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + website.set_extra_links(HashSet::from([(website_url.to_owned() + + "/search/howsearchworks/?fg=1") + .into()])); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{}\n{}", page.get_url(), page.get_html()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/loop.rs b/examples/loop.rs new file mode 100644 index 000000000..206ece113 --- /dev/null +++ b/examples/loop.rs @@ -0,0 +1,75 @@ +//! `cargo run --example loop --features="spider/sync spider/smart"` +extern crate spider; + +use spider::website::Website; +use spider::{configuration, tokio}; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut configuration = configuration::Configuration::new(); + + configuration.with_limit(250); + + let website_list = vec!["https://choosealicense.com", "https://spider.cloud"]; + + let mut tasks = Vec::new(); + + for u in website_list { + let configuration = configuration.clone(); + + tasks.push(tokio::spawn(async move { + let mut website: Website = Website::new(u).with_config(configuration).build().unwrap(); + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + + let mut stdout = tokio::io::stdout(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + stdout + }); + + let start = std::time::Instant::now(); + + website.crawl_raw().await; + website.unsubscribe(); + let duration = start.elapsed(); + let mut stdout = join_handle.await.unwrap(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}\n", + duration, + website.get_size().await + ) + .as_bytes(), + ) + .await; + })); + } + + let start = std::time::Instant::now(); + + for task in tasks { + let _ = task.await; + } + + let duration = start.elapsed(); + let mut stdout = tokio::io::stdout(); + + let _ = stdout + .write_all( + format!( + "Total time elapsed for all website.crawl() is: {:?}\n", + duration, + ) + .as_bytes(), + ) + .await; +} diff --git a/examples/not_a_robot.rs b/examples/not_a_robot.rs new file mode 100644 index 000000000..5f49fd057 --- /dev/null +++ b/examples/not_a_robot.rs @@ -0,0 +1,282 @@ +//! Not A Robot challenge test using Claude via OpenRouter. +//! +//! This example tests the multimodal automation capabilities against +//! neal.fun/not-a-robot - a fun interactive challenge where you prove +//! you're not a robot by completing increasingly complex tasks. +//! +//! Run with: +//! ```bash +//! cargo run --example not_a_robot --features "spider/sync spider/chrome spider/chrome_headed spider/agent_chrome spider/agent_skills" +//! ``` +//! +//! The API key is read from `.env` file (OPEN_ROUTER=...) or the environment. + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; +use std::fs; +use std::path::Path; +use std::time::Duration; + +/// Load environment variables from a `.env` file if present. +fn load_dotenv() { + let paths = [".env", "../.env"]; + for path in &paths { + if let Ok(contents) = fs::read_to_string(path) { + for line in contents.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some((key, value)) = line.split_once('=') { + let key = key.trim(); + let value = value.trim(); + // Only set if not already present in environment + if std::env::var(key).is_err() { + std::env::set_var(key, value); + } + } + } + break; + } + } +} + +#[tokio::main] +async fn main() { + // Load .env file first, then init logging + load_dotenv(); + + // Enable debug logging to see model responses and actions + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); + + // Get API key from environment variable (loaded from .env or shell) + let api_key = std::env::var("OPEN_ROUTER") + .expect("OPEN_ROUTER environment variable must be set (set in .env or shell)"); + + // Create output directory for screenshots + let output_dir = Path::new("not_a_robot_results"); + if !output_dir.exists() { + fs::create_dir_all(output_dir).expect("Failed to create output directory"); + } + + // Target URL - the "I'm Not A Robot" challenge + let url = "https://neal.fun/not-a-robot/"; + + // Configure remote multimodal with OpenRouter + // Default to Claude Opus 4.6 (frontier model, excellent vision + reasoning). + // Override with MODEL env var for other models. + let model = std::env::var("MODEL").unwrap_or_else(|_| "anthropic/claude-opus-4.6".to_string()); + let mut mm_config = + RemoteMultimodalConfigs::new("https://openrouter.ai/api/v1/chat/completions", &model); + + // Set the API key + mm_config.api_key = Some(api_key); + + // Minimal task - the default system prompt handles challenge types + mm_config.user_message_extra = Some( + "Complete ALL levels of this challenge. Track progress using memory_ops and extracted fields. Report current_level number and level_name in extracted. Limit to at most 5 solve attempts per level; if still on same level after 5 attempts, change strategy (refresh/re-evaluate) before continuing." + .to_string(), + ); + + // Configure for interactive challenge completion + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 200; // Allow many rounds for multiple challenge levels + mm_config.cfg.request_json_object = true; + mm_config.cfg.post_plan_wait_ms = 1500; // Wait between rounds for UI transitions + mm_config.cfg.screenshot = true; + mm_config.cfg.best_effort_json_extract = true; + mm_config.cfg.max_tokens = 4096; // Enough for detailed responses with coordinates + mm_config.cfg.temperature = 0.1; + + // Built-in web challenge skills auto-load when the `agent_skills` feature is enabled. + + let max_rounds = mm_config.cfg.max_rounds; + + // Create a viewport with 2x device scale factor - larger viewport for better grid/text visibility + let mut viewport = spider::configuration::Viewport::new(1440, 1080); + viewport.set_scale_factor(Some(2.0)); + + // Optional proxy (set PROXY env var, e.g. PROXY=http://localhost:8888) + let proxy = std::env::var("PROXY").ok(); + + // Create website with Chrome in headed mode + let mut website: Website = Website::new(url) + .with_viewport(Some(viewport)) + .with_limit(1) + .with_request_timeout(None) // Disable request timeout - automation has its own max_rounds + .with_chrome_intercept( + spider::features::chrome_common::RequestInterceptConfiguration::new(true), + ) + .with_wait_for_idle_network(Some(spider::configuration::WaitForIdleNetwork::new(Some( + Duration::from_secs(30), + )))) + .with_proxies(proxy.map(|p| vec![p])) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Subscribe to receive pages + let mut rx = website.subscribe(16).unwrap(); + + // Clone output_dir path for the spawned task + let output_dir_clone = output_dir.to_path_buf(); + + // Spawn task to handle received pages and save screenshots + let join_handle = tokio::spawn(async move { + let mut levels_completed: Vec = Vec::new(); + let mut highest_level_seen = 0u32; + + while let Ok(page) = rx.recv().await { + println!("\n=================================================="); + println!("=== Challenge Progress ==="); + println!("URL: {}", page.get_url()); + + // Check for remote multimodal automation results + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Automation Rounds ==="); + + for (i, result) in ai_data.iter().enumerate() { + println!("\nRound {}:", i + 1); + println!(" Label: {}", result.input); + + let parsed = &result.content_output; + if !parsed.is_null() { + if let Some(extracted) = parsed.get("extracted") { + let current_level = extracted + .get("current_level") + .and_then(|v| v.as_u64()) + .unwrap_or(0) + as u32; + + let level_name = extracted + .get("level_name") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + + let level_completed = extracted + .get("level_completed") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let challenge_complete = extracted + .get("challenge_complete") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + println!(" Current Level: {} - {}", current_level, level_name); + + if current_level > highest_level_seen { + if highest_level_seen > 0 { + println!( + " LEVEL {} COMPLETED! Advanced to Level {}", + highest_level_seen, current_level + ); + levels_completed.push(highest_level_seen); + } + highest_level_seen = current_level; + } + + if level_completed { + println!(" Level marked as completed"); + } + + if challenge_complete { + println!(" CHALLENGE COMPLETE!"); + } + } + + if let Some(steps) = parsed.get("steps") { + println!(" Steps: {}", steps); + } + } + + if let Some(ref usage) = result.usage { + println!( + " Tokens: {} prompt, {} completion", + usage.prompt_tokens, usage.completion_tokens + ); + } + + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + } + } + + // Save final screenshot + if let Some(ref screenshot_b64) = page.screenshot_bytes { + let screenshot_path = output_dir_clone.join("final_screenshot.png"); + if fs::write(&screenshot_path, screenshot_b64).is_ok() { + println!("\nFinal screenshot saved to: {}", screenshot_path.display()); + } + } + + // Print summary + println!("\n=== Level Progression Summary ==="); + println!("Highest level reached: {}", highest_level_seen); + println!("Levels completed: {:?}", levels_completed); + + if let Some(ref usage) = page.remote_multimodal_usage { + println!("\n=== Total Usage ==="); + let mut total_prompt = 0u32; + let mut total_completion = 0u32; + let mut total_calls = 0u32; + for u in usage.iter() { + total_prompt += u.prompt_tokens; + total_completion += u.completion_tokens; + total_calls += u.llm_calls; + } + println!("LLM Calls: {}", total_calls); + println!("Prompt Tokens: {}", total_prompt); + println!("Completion Tokens: {}", total_completion); + println!("Total Tokens: {}", total_prompt + total_completion); + } + } + + levels_completed + }); + + // Start the automation + println!("=========================================="); + println!(" 'I'm Not A Robot' Challenge Test"); + println!("=========================================="); + println!("URL: {}", url); + println!("Model: {}", model); + println!("Max rounds: {}", max_rounds); + println!("Output dir: {}", output_dir.display()); + println!("==========================================\n"); + + let start = tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + // Unsubscribe to close the channel + website.unsubscribe(); + + // Wait for the spawned task to complete and get results + match join_handle.await { + Ok(levels_completed) => { + println!("\n=========================================="); + println!(" CHALLENGE RESULTS"); + println!("=========================================="); + println!("Total time: {:?}", duration); + println!("Levels completed: {}", levels_completed.len()); + for level in &levels_completed { + println!(" Level {} completed", level); + } + if levels_completed.is_empty() { + println!(" No levels were completed"); + } + println!("=========================================="); + } + Err(e) => { + println!("\nError in handler task: {}", e); + } + } +} diff --git a/examples/not_a_robot_chrome_ai.rs b/examples/not_a_robot_chrome_ai.rs new file mode 100644 index 000000000..2274a9964 --- /dev/null +++ b/examples/not_a_robot_chrome_ai.rs @@ -0,0 +1,242 @@ +//! Not A Robot challenge using Chrome's built-in AI (Gemini Nano). +//! +//! This example runs the "I'm Not A Robot" challenge using Chrome's +//! built-in LanguageModel API (Gemini Nano) for inference — no external +//! API key required. +//! +//! ## Prerequisites +//! +//! **Chrome Canary or Dev channel is required.** The LanguageModel API +//! (Prompt API) is not available in Chrome stable — it requires the +//! "Optimization Guide On Device Model" component which is only present +//! in Canary/Dev. +//! +//! ### 1. Install Chrome Canary +//! +//! Download from +//! +//! ### 2. Enable flags in Chrome Canary +//! +//! 1. Open `chrome://flags/#optimization-guide-on-device-model` → **Enabled BypassPerfRequirement** +//! 2. Open `chrome://flags/#prompt-api-for-gemini-nano` → **Enabled** +//! 3. Restart Chrome Canary +//! +//! ### 3. Verify the model is downloaded +//! +//! 1. Open `chrome://components` → find "Optimization Guide On Device Model" +//! - If version is `0.0.0.0`, click "Check for update" and wait +//! - The model is ~1-2 GB and may take several minutes to download +//! 2. Open `chrome://on-device-internals` → "Model Status" tab +//! - Foundational model state should be **Ready** +//! 3. Test in DevTools console: `await LanguageModel.availability()` → should return `"available"` +//! +//! **Hardware requirements:** 22 GB free storage, 4 GB+ VRAM or 16 GB RAM with 4+ CPU cores. +//! +//! ## Run +//! +//! Option A — Connect to Chrome Canary with remote debugging (recommended): +//! ```bash +//! # 1. Launch Chrome Canary with AI flags: +//! /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary \ +//! --remote-debugging-port=9222 \ +//! --enable-features=OptimizationGuideOnDeviceModel:BypassPerfRequirement/true,PromptAPIForGeminiNano,PromptAPIForGeminiNanoMultimodalInput \ +//! --no-first-run --user-data-dir=$HOME/.chrome-ai-profile & +//! +//! # 2. Get the WebSocket URL: +//! curl -s http://localhost:9222/json/version | jq -r .webSocketDebuggerUrl +//! +//! # 3. Run the example: +//! CHROME_URL=ws://localhost:9222/devtools/browser/ \ +//! cargo run --example not_a_robot_chrome_ai --features "spider/sync spider/chrome spider/chrome_headed spider/agent_chrome spider/agent_skills" +//! ``` +//! +//! Option B — Let spider launch Chrome (adds AI flags automatically when `use_chrome_ai` is set): +//! ```bash +//! cargo run --example not_a_robot_chrome_ai --features "spider/sync spider/chrome spider/chrome_headed spider/agent_chrome spider/agent_skills" +//! ``` + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; +use std::fs; +use std::path::Path; +use std::time::Duration; + +#[tokio::main] +async fn main() { + // Enable debug logging to see model responses and actions + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); + + // Create output directory for screenshots + let output_dir = Path::new("not_a_robot_chrome_ai_results"); + if !output_dir.exists() { + fs::create_dir_all(output_dir).expect("Failed to create output directory"); + } + + // Target URL + let url = "https://neal.fun/not-a-robot/"; + + // Configure with Chrome AI — no API URL or key needed. + // We pass empty strings since Chrome AI bypasses the HTTP path entirely. + let mut mm_config = RemoteMultimodalConfigs::new("", "chrome-ai"); + mm_config.use_chrome_ai = true; + + // Task instructions — kept concise for the smaller model + mm_config.user_message_extra = Some( + "Complete ALL levels. Track progress in memory_ops. Report current_level and level_name in extracted. Max 5 attempts per level; change strategy after 5 fails." + .to_string(), + ); + + // Chrome AI context budget — Gemini Nano has limited context + mm_config.chrome_ai_max_user_chars = 5000; + + // Configure for interactive challenge completion + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.html_max_bytes = 4000; // Chrome AI has less context — don't waste on huge HTML + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 200; + mm_config.cfg.request_json_object = true; + mm_config.cfg.post_plan_wait_ms = 1500; + mm_config.cfg.screenshot = true; + mm_config.cfg.best_effort_json_extract = true; + mm_config.cfg.max_tokens = 4096; + mm_config.cfg.temperature = 0.1; + + // Create a viewport with 2x scale factor + let mut viewport = spider::configuration::Viewport::new(1440, 1080); + viewport.set_scale_factor(Some(2.0)); + + // Connect to an existing Chrome via CHROME_URL env var, or launch a new one. + // When using Chrome AI, connecting to a pre-launched Chrome with the AI flags + // is the most reliable approach. + let chrome_url = std::env::var("CHROME_URL").ok(); + if let Some(ref u) = chrome_url { + println!("Connecting to Chrome at: {}", u); + } + + let mut website: Website = Website::new(url) + .with_chrome_connection(chrome_url) + .with_viewport(Some(viewport)) + .with_limit(1) + .with_request_timeout(None) + .with_chrome_intercept( + spider::features::chrome_common::RequestInterceptConfiguration::new(true), + ) + .with_wait_for_idle_network(Some(spider::configuration::WaitForIdleNetwork::new(Some( + Duration::from_secs(30), + )))) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Subscribe to receive pages + let mut rx = website.subscribe(16).unwrap(); + + let output_dir_clone = output_dir.to_path_buf(); + + let join_handle = tokio::spawn(async move { + let mut levels_completed: Vec = Vec::new(); + let mut highest_level_seen = 0u32; + + while let Ok(page) = rx.recv().await { + println!("\n=================================================="); + println!("=== Challenge Progress (Chrome AI) ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Automation Rounds ==="); + + for (i, result) in ai_data.iter().enumerate() { + println!("\nRound {}:", i + 1); + println!(" Label: {}", result.input); + + let parsed = &result.content_output; + if !parsed.is_null() { + if let Some(extracted) = parsed.get("extracted") { + let current_level = extracted + .get("current_level") + .and_then(|v| v.as_u64()) + .unwrap_or(0) + as u32; + + let level_name = extracted + .get("level_name") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + + println!(" Current Level: {} - {}", current_level, level_name); + + if current_level > highest_level_seen { + if highest_level_seen > 0 { + println!( + " LEVEL {} COMPLETED! Advanced to Level {}", + highest_level_seen, current_level + ); + levels_completed.push(highest_level_seen); + } + highest_level_seen = current_level; + } + } + } + + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + } + } + + // Save final screenshot + if let Some(ref screenshot_b64) = page.screenshot_bytes { + let screenshot_path = output_dir_clone.join("final_screenshot.png"); + if fs::write(&screenshot_path, screenshot_b64).is_ok() { + println!("\nFinal screenshot saved to: {}", screenshot_path.display()); + } + } + + println!("\n=== Level Progression Summary ==="); + println!("Highest level reached: {}", highest_level_seen); + println!("Levels completed: {:?}", levels_completed); + } + + levels_completed + }); + + // Start the automation + println!("=========================================="); + println!(" 'Not A Robot' Challenge (Chrome AI)"); + println!("=========================================="); + println!("URL: {}", url); + println!("Model: Chrome built-in LanguageModel (Gemini Nano)"); + println!("Output dir: {}", output_dir.display()); + println!("==========================================\n"); + + let start = tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + website.unsubscribe(); + + match join_handle.await { + Ok(levels_completed) => { + println!("\n=========================================="); + println!(" CHALLENGE RESULTS (Chrome AI)"); + println!("=========================================="); + println!("Total time: {:?}", duration); + println!("Levels completed: {}", levels_completed.len()); + for level in &levels_completed { + println!(" Level {} completed", level); + } + if levels_completed.is_empty() { + println!(" No levels were completed"); + } + println!("=========================================="); + } + Err(e) => { + println!("\nError in handler task: {}", e); + } + } +} diff --git a/examples/not_a_robot_haiku.rs b/examples/not_a_robot_haiku.rs new file mode 100644 index 000000000..12f05e7a5 --- /dev/null +++ b/examples/not_a_robot_haiku.rs @@ -0,0 +1,259 @@ +//! Not A Robot challenge test using Claude Haiku (cheaper model). +//! +//! This example tests how far a cheaper/faster model can get through +//! the neal.fun/not-a-robot challenge levels. Haiku is much faster and +//! cheaper than Opus, making it ideal for benchmarking minimum model +//! capabilities needed for each level. +//! +//! Run with: +//! ```bash +//! cargo run --example not_a_robot_haiku --features "spider/sync spider/chrome spider/chrome_headed spider/agent_chrome spider/agent_skills" +//! ``` +//! +//! The API key is read from `.env` file (OPEN_ROUTER=...) or the environment. + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; +use std::fs; +use std::path::Path; +use std::time::Duration; + +/// Load environment variables from a `.env` file if present. +fn load_dotenv() { + let paths = [".env", "../.env"]; + for path in &paths { + if let Ok(contents) = fs::read_to_string(path) { + for line in contents.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some((key, value)) = line.split_once('=') { + let key = key.trim(); + let value = value.trim(); + if std::env::var(key).is_err() { + std::env::set_var(key, value); + } + } + } + break; + } + } +} + +#[tokio::main] +async fn main() { + load_dotenv(); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init(); + + let api_key = std::env::var("OPEN_ROUTER") + .expect("OPEN_ROUTER environment variable must be set (set in .env or shell)"); + + let output_dir = Path::new("not_a_robot_haiku_results"); + if !output_dir.exists() { + fs::create_dir_all(output_dir).expect("Failed to create output directory"); + } + + let url = "https://neal.fun/not-a-robot/"; + + // Use Claude Haiku — fast and cheap, good baseline test. + // Override with MODEL env var for other models (e.g. MODEL=anthropic/claude-sonnet-4-5). + let model = std::env::var("MODEL") + .unwrap_or_else(|_| "anthropic/claude-haiku-4-5-20251001".to_string()); + let mut mm_config = + RemoteMultimodalConfigs::new("https://openrouter.ai/api/v1/chat/completions", &model); + + mm_config.api_key = Some(api_key); + + mm_config.user_message_extra = Some( + "Complete ALL levels of this challenge. Track progress using memory_ops and extracted fields. Report current_level number and level_name in extracted. Limit to at most 5 solve attempts per level; if still on same level after 5 attempts, change strategy (refresh/re-evaluate) before continuing." + .to_string(), + ); + + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 200; + mm_config.cfg.request_json_object = true; + mm_config.cfg.post_plan_wait_ms = 1500; + mm_config.cfg.screenshot = true; + mm_config.cfg.best_effort_json_extract = true; + mm_config.cfg.max_tokens = 4096; + mm_config.cfg.temperature = 0.1; + + let max_rounds = mm_config.cfg.max_rounds; + + let mut viewport = spider::configuration::Viewport::new(1440, 1080); + viewport.set_scale_factor(Some(2.0)); + + let proxy = std::env::var("PROXY").ok(); + + let mut website: Website = Website::new(url) + .with_viewport(Some(viewport)) + .with_limit(1) + .with_request_timeout(None) + .with_chrome_intercept( + spider::features::chrome_common::RequestInterceptConfiguration::new(true), + ) + .with_wait_for_idle_network(Some(spider::configuration::WaitForIdleNetwork::new(Some( + Duration::from_secs(30), + )))) + .with_proxies(proxy.map(|p| vec![p])) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + let mut rx = website.subscribe(16).unwrap(); + + let output_dir_clone = output_dir.to_path_buf(); + + let join_handle = tokio::spawn(async move { + let mut levels_completed: Vec = Vec::new(); + let mut highest_level_seen = 0u32; + + while let Ok(page) = rx.recv().await { + println!("\n=================================================="); + println!("=== Haiku Challenge Progress ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Automation Rounds ==="); + + for (i, result) in ai_data.iter().enumerate() { + println!("\nRound {}:", i + 1); + println!(" Label: {}", result.input); + + let parsed = &result.content_output; + if !parsed.is_null() { + if let Some(extracted) = parsed.get("extracted") { + let current_level = extracted + .get("current_level") + .and_then(|v| v.as_u64()) + .unwrap_or(0) + as u32; + + let level_name = extracted + .get("level_name") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown"); + + let level_completed = extracted + .get("level_completed") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let challenge_complete = extracted + .get("challenge_complete") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + println!(" Current Level: {} - {}", current_level, level_name); + + if current_level > highest_level_seen { + if highest_level_seen > 0 { + println!( + " LEVEL {} COMPLETED! Advanced to Level {}", + highest_level_seen, current_level + ); + levels_completed.push(highest_level_seen); + } + highest_level_seen = current_level; + } + + if level_completed { + println!(" Level marked as completed"); + } + + if challenge_complete { + println!(" CHALLENGE COMPLETE!"); + } + } + + if let Some(steps) = parsed.get("steps") { + println!(" Steps: {}", steps); + } + } + + if let Some(ref usage) = result.usage { + println!( + " Tokens: {} prompt, {} completion", + usage.prompt_tokens, usage.completion_tokens + ); + } + + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + } + } + + if let Some(ref screenshot_b64) = page.screenshot_bytes { + let screenshot_path = output_dir_clone.join("final_screenshot.png"); + if fs::write(&screenshot_path, screenshot_b64).is_ok() { + println!("\nFinal screenshot saved to: {}", screenshot_path.display()); + } + } + + println!("\n=== Haiku Level Progression Summary ==="); + println!("Highest level reached: {}", highest_level_seen); + println!("Levels completed: {:?}", levels_completed); + + if let Some(ref usage) = page.remote_multimodal_usage { + println!("\n=== Total Usage (Haiku) ==="); + let mut total_prompt = 0u32; + let mut total_completion = 0u32; + let mut total_calls = 0u32; + for u in usage.iter() { + total_prompt += u.prompt_tokens; + total_completion += u.completion_tokens; + total_calls += u.llm_calls; + } + println!("LLM Calls: {}", total_calls); + println!("Prompt Tokens: {}", total_prompt); + println!("Completion Tokens: {}", total_completion); + println!("Total Tokens: {}", total_prompt + total_completion); + } + } + + levels_completed + }); + + println!("=========================================="); + println!(" 'I'm Not A Robot' Haiku Benchmark"); + println!("=========================================="); + println!("URL: {}", url); + println!("Model: {} (cheap/fast)", model); + println!("Max rounds: {}", max_rounds); + println!("Output dir: {}", output_dir.display()); + println!("==========================================\n"); + + let start = tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + website.unsubscribe(); + + match join_handle.await { + Ok(levels_completed) => { + println!("\n=========================================="); + println!(" HAIKU BENCHMARK RESULTS"); + println!("=========================================="); + println!("Total time: {:?}", duration); + println!("Levels completed: {}", levels_completed.len()); + for level in &levels_completed { + println!(" Level {} completed", level); + } + if levels_completed.is_empty() { + println!(" No levels were completed"); + } + println!("=========================================="); + } + Err(e) => { + println!("\nError in handler task: {}", e); + } + } +} diff --git a/examples/openai.rs b/examples/openai.rs new file mode 100644 index 000000000..85ce61605 --- /dev/null +++ b/examples/openai.rs @@ -0,0 +1,79 @@ +//! `OPENAI_API_KEY=$MYAPI_KEY cargo run --example openai --features openai` +extern crate spider; + +use std::time::Duration; + +use spider::configuration::{GPTConfigs, WaitForIdleNetwork}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::hashbrown::{HashMap, HashSet}; +use spider::website::Website; +use spider::{tokio, CaseInsensitiveString}; + +#[tokio::main] +async fn main() { + let _ = tokio::fs::create_dir_all("./storage/").await; + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let website_url = "https://www.google.com"; + + let mut gpt_config = GPTConfigs::default(); + let prompt_url_map = HashMap::from([ + ( + CaseInsensitiveString::new(website_url), + Box::new(GPTConfigs::new( + "gpt-4o-2024-05-13", + "Search for Movies", + 500, + )), + ), + ( + CaseInsensitiveString::new( + &((website_url.to_owned()) + "/search/howsearchworks/?fg=1"), + ), + Box::new(GPTConfigs::new( + "gpt-4o-2024-05-13", + "Change the background blue", + 500, + )), + ), + ]); + + gpt_config.prompt_url_map = Some(Box::new(prompt_url_map)); + + let mut website: Website = Website::new(website_url) + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_screenshot(Some(screenshot_config)) + .with_limit(2) + .with_stealth(true) + .with_fingerprint(true) + // .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .with_openai(Some(gpt_config)) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + website.set_extra_links(HashSet::from([(website_url.to_owned() + + "/search/howsearchworks/?fg=1") + .into()])); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{}\n{}", page.get_url(), page.get_html()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/openai_cache.rs b/examples/openai_cache.rs new file mode 100644 index 000000000..2d1042580 --- /dev/null +++ b/examples/openai_cache.rs @@ -0,0 +1,73 @@ +//! `OPENAI_API_KEY=$MYAPI_KEY cargo run --example openai_cache --features="spider/sync spider/openai spider/cache_openai"`` +extern crate spider; + +use spider::configuration::{GPTConfigs, WaitForIdleNetwork}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::moka::future::Cache; +use spider::tokio; +use spider::website::Website; +use std::time::Duration; + +#[tokio::main] +async fn main() { + let _ = tokio::fs::create_dir_all("./storage/").await; + + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + // params that handle the way to take screenshots + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let cache = Cache::builder() + .time_to_live(Duration::from_secs(30 * 60)) + .time_to_idle(Duration::from_secs(5 * 60)) + .max_capacity(10_000) + .build(); + + let mut gpt_config: GPTConfigs = GPTConfigs::new_multi_cache( + "gpt-4o", + vec![ + "Search for Movies", + "Click on the first result movie result", + ], + 500, + Some(cache), + ); + gpt_config.set_extra(true); + + let mut website: Website = Website::new("https://www.google.com") + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_screenshot(Some(screenshot_config)) + .with_limit(1) + .with_openai(Some(gpt_config)) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + let handle = tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{}\n{:?}", page.get_url(), page.openai_credits_used); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + + // crawl the page again to see if cache is re-used. + website.crawl().await; + + website.unsubscribe(); + + let _ = handle.await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/openai_extra.rs b/examples/openai_extra.rs new file mode 100644 index 000000000..1cc169acb --- /dev/null +++ b/examples/openai_extra.rs @@ -0,0 +1,58 @@ +//! `OPENAI_API_KEY=$MYAPI_KEY cargo run --example openai_extra --features openai` +extern crate spider; + +use spider::configuration::{GPTConfigs, WaitForIdleNetwork}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::time::Duration; + +#[tokio::main] +async fn main() { + let _ = tokio::fs::create_dir_all("./storage/").await; + + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let mut gpt_config: GPTConfigs = GPTConfigs::new_multi( + "gpt-4o", + vec!["Search for Movies", "Extract the hrefs found."], + 3000, + ); + + gpt_config.screenshot = false; + gpt_config.set_extra(true); + + let mut website: Website = Website::new("https://www.bing.com") + .with_chrome_intercept(RequestInterceptConfiguration::new(true)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_screenshot(Some(screenshot_config)) + .with_limit(1) + .with_stealth(true) + .with_fingerprint(true) + .with_limit(1) + .with_openai(Some(gpt_config)) + // .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{}\n{:?}", page.get_url(), page.extra_ai_data); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/openai_multi.rs b/examples/openai_multi.rs new file mode 100644 index 000000000..e5f95bca7 --- /dev/null +++ b/examples/openai_multi.rs @@ -0,0 +1,58 @@ +//! `OPENAI_API_KEY=$MYAPI_KEY cargo run --example openai_multi --features openai` +extern crate spider; + +use spider::configuration::{GPTConfigs, WaitForIdleNetwork}; +use spider::features::chrome_common::RequestInterceptConfiguration; +use spider::tokio; +use spider::website::Website; +use std::time::Duration; + +#[tokio::main] +async fn main() { + let _ = tokio::fs::create_dir_all("./storage/").await; + + let screenshot_params = + spider::configuration::ScreenshotParams::new(Default::default(), Some(true), Some(false)); + // params that handle the way to take screenshots + let screenshot_config = + spider::configuration::ScreenShotConfig::new(screenshot_params, true, true, None); + + let gpt_config: GPTConfigs = GPTConfigs::new_multi( + "gpt-4o", + vec![ + "Search for Movies", + "Click on the first result movie result", + ], + 500, + ); + + let mut website: Website = Website::new("https://www.google.com") + .with_chrome_intercept(RequestInterceptConfiguration::new(false)) + .with_wait_for_idle_network(Some(WaitForIdleNetwork::new(Some(Duration::from_secs(30))))) + .with_screenshot(Some(screenshot_config)) + .with_stealth(true) + .with_fingerprint(true) + .with_limit(1) + .with_openai(Some(gpt_config)) + // .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{}\n{}", page.get_url(), page.get_html()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/queue.rs b/examples/queue.rs new file mode 100644 index 000000000..a1e52757d --- /dev/null +++ b/examples/queue.rs @@ -0,0 +1,48 @@ +//! `cargo run --example queue` +extern crate spider; + +use spider::tokio; +use spider::url::Url; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + let mut rx2 = website.subscribe(16).unwrap(); + let mut g = website.subscribe_guard().unwrap(); + let q = website.queue(100).unwrap(); + + tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let u = res.get_url(); + println!("{:?}", u); + let mut url = Url::parse(u).expect("Failed to parse URL"); + + let mut segments: Vec<_> = url + .path_segments() + .map(|c| c.collect::>()) + .unwrap_or_default(); + + if !segments.is_empty() && segments[0] == "en" { + segments[0] = "fr"; + let new_path = segments.join("/"); + url.set_path(&new_path); + // get a new url here or perform an action and queue links + // pre-fetch all fr locales + let _ = q.send(url.into()); + } + + g.inc(); + } + }); + + let start = std::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_size().await + ) +} diff --git a/examples/real_world.rs b/examples/real_world.rs new file mode 100644 index 000000000..414b24406 --- /dev/null +++ b/examples/real_world.rs @@ -0,0 +1,112 @@ +//! cargo run --example real_world --features="chrome chrome_intercept real_browser" + +extern crate spider; +use crate::spider::tokio::io::AsyncWriteExt; +use spider::client::header::REFERER; +use spider::client::header::{HeaderMap, HeaderValue}; +use spider::configuration::WaitForSelector; +use spider::tokio; +use spider::website::Website; +use spider::{ + configuration::{ChromeEventTracker, WaitForIdleNetwork}, + features::chrome_common::RequestInterceptConfiguration, +}; +use std::io::Result; +use std::iter::FromIterator; +use std::time::Duration; +async fn crawl_website(url: &str) -> Result<()> { + let mut stdout = tokio::io::stdout(); + let mut interception = RequestInterceptConfiguration::new(true); + let mut tracker = ChromeEventTracker::default(); + + interception.block_javascript = true; + + tracker.responses = true; + tracker.requests = true; + + let mut website: Website = Website::new(url) + .with_limit(5) + .with_chrome_intercept(interception) + .with_wait_for_idle_network0(Some(WaitForIdleNetwork::new(Some(Duration::from_millis( + 3000, + ))))) + .with_wait_for_idle_dom(Some(WaitForSelector::new( + Some(Duration::from_millis(100)), + "body".into(), + ))) + .with_headers(Some(HeaderMap::from_iter([( + REFERER, + HeaderValue::from_static(spider::spider_fingerprint::spoof_referrer()), + )]))) + .with_block_assets(true) + // .with_wait_for_delay(Some(WaitForDelay::new(Some(Duration::from_millis(10000))))) + .with_stealth(true) + .with_return_page_links(true) + .with_fingerprint(true) + .with_event_tracker(Some(tracker)) + // .with_proxies(Some(vec!["http://localhost:8888".into()])) + .with_chrome_connection(Some("http://127.0.0.1:9222/json/version".into())) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + let mut g = website.subscribe_guard().unwrap(); + + let start = crate::tokio::time::Instant::now(); + + g.guard(true); + + let (links, _) = tokio::join!( + async move { + website.crawl_smart().await; + website.unsubscribe(); + website.get_all_links_visited().await + }, + async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "---- {}\nBytes transferred {:?}\nHTML Size {:?}\nLinks: {:?}\nRequests Sent {:?}\nResponses {:?}\nMetadata {:?}\n", + page.get_url(), + page.bytes_transferred.unwrap_or_default(), + page.get_html_bytes_u8().len(), + match page.page_links { + Some(ref l) => l.len(), + _ => 0, + }, + page.get_request().as_ref().map(|f| f.len()), + page.get_responses(), + page.get_metadata() + ) + .as_bytes(), + ) + .await; + g.inc(); + } + } + ); + + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + url, + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://example.com"), + crawl_website("https://rust-lang.org"), + ); + + Ok(()) +} diff --git a/examples/remote_multimodal.rs b/examples/remote_multimodal.rs new file mode 100644 index 000000000..bb178b869 --- /dev/null +++ b/examples/remote_multimodal.rs @@ -0,0 +1,137 @@ +//! Remote multimodal automation example using OpenRouter. +//! +//! This example demonstrates how to use a remote multimodal model (via OpenRouter) +//! to extract structured data from web pages. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal --features "spider/sync spider/chrome" +//! ``` + +// EXAMPLE output +// === Page Received === +// URL: https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html + +// === AI Extraction Results === +// Result 1: +// Input prompt: extract_book_details +// Content output: {"book":{"availability":"In stock (22 available)","description":"It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary with this special edition. Silverstein's humorous and creative verse can amuse the dowdiest of readers. Lemon-faced adults and fidgety kids sit still and read these rhythmic words and laugh and smile and love that Silverstein. Need proof of his genius? RockabyeRockabye baby, in the treetopDon't you know a treetopIs no safe place to rock?And who put you up there,And your cradle, too?Baby, I think someone down here'sGot it in for you. Shel, you never sounded so good.","number_of_reviews":"0","price":"£51.77","price_excl_tax":"£51.77","price_incl_tax":"£51.77","product_type":"Books","tax":"£0.00","title":"A Light in the Attic","upc":"a897fe39b1053632"}} +// Usage: AutomationUsage { prompt_tokens: 3223, completion_tokens: 312, total_tokens: 3535, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +// === Remote Multimodal Usage === +// Usage 1: AutomationUsage { prompt_tokens: 3223, completion_tokens: 312, total_tokens: 3535, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +// === Raw HTML (truncated) === +// +// +// A Light in the Attic | Books to Scrape - Sandbox +// + +// +// ... + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + // Get API key from environment variable + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + // Target URL - a book details page + let url = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"; + + // Configure remote multimodal with OpenRouter + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", // OpenRouter model identifier + ); + + // Set the API key + mm_config.api_key = Some(api_key); + + // Set up extraction prompt + mm_config.system_prompt = Some( + "Extract the following data from the webpage content and return as valid JSON: \ + Extract book details including title, price, availability, description, and any other relevant information." + .to_string(), + ); + + // Configure extraction settings + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; // Single extraction pass + mm_config.cfg.request_json_object = true; + + // Create website with remote multimodal config + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Subscribe to receive pages + let mut rx = website.subscribe(16).unwrap(); + + // Spawn task to handle received pages + let join_handle = tokio::spawn(async move { + while let Ok(page) = rx.recv().await { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + // Check for remote multimodal extraction results + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Input prompt: {}", result.input); + println!(" Content output: {}", result.content_output); + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + } + } else { + println!("\nNo AI extraction data available."); + } + + // Check for remote multimodal usage statistics + if let Some(ref usage) = page.remote_multimodal_usage { + println!("\n=== Remote Multimodal Usage ==="); + for (i, u) in usage.iter().enumerate() { + println!("Usage {}: {:?}", i + 1, u); + } + } + + println!("\n=== Raw HTML (truncated) ==="); + let html = page.get_html(); + if html.len() > 500 { + println!("{}...", &html[..500]); + } else { + println!("{}", html); + } + } + }); + + // Start scraping + let start = tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + // Unsubscribe to close the channel and let the spawned task exit + website.unsubscribe(); + + // Wait for the spawned task to complete + let _ = join_handle.await; + + println!("\n=== Completed ==="); + println!("Time elapsed: {:?}", duration); +} diff --git a/examples/remote_multimodal_benchmark.rs b/examples/remote_multimodal_benchmark.rs new file mode 100644 index 000000000..a0333244b --- /dev/null +++ b/examples/remote_multimodal_benchmark.rs @@ -0,0 +1,472 @@ +//! Remote multimodal model benchmark example. +//! +//! This example benchmarks multiple LLM models via OpenRouter for extraction tasks, +//! comparing speed, token usage, and cost across top-tier and budget models. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_benchmark --features "spider/sync spider/chrome" +//! ``` + +// ================================================================================================================================== +// BENCHMARK RESULTS +// ================================================================================================================================== + +// Model Tier Time(s) Prompt Comp Total Cost($) Quality Status +// ---------------------------------------------------------------------------------------------------------------------------------- +// GPT-4o top-tier 15.04 2681 294 2975 0.009643 100% ✓ +// Claude 3.5 Sonnet top-tier 12.86 2439 284 2723 0.011577 100% ✓ +// Gemini 2.0 Flash top-tier 6.21 3606 427 4033 0.000531 100% ✓ +// Qwen2-VL 72B top-tier 11.16 2578 312 2890 0.001156 100% ✓ +// Gemini 2.0 Flash Lite budget 5.95 3606 419 4025 0.000396 100% ✓ +// Gemini 1.5 Flash budget 2.53 0 0 0 0.000000 0% ✗ +// └─ Error: Page fetched (9308B) but no AI data +// Qwen2-VL 7B budget 22.28 2907 315 3222 0.000322 100% ✓ +// Pixtral 12B budget 7.08 3838 511 4349 0.000435 100% ✓ +// ---------------------------------------------------------------------------------------------------------------------------------- + +// SUMMARY (successful runs only): +// 🏆 Fastest: Gemini 2.0 Flash Lite (5.95s) +// 💰 Cheapest: Qwen2-VL 7B ($0.000322) +// ⚡ Highest throughput: Pixtral 12B (72.2 tokens/s) +// 🎯 Best value: Qwen2-VL 7B (100% quality, $0.000322) +// 🌟 Highest quality: Pixtral 12B (100%) + +// TIER COMPARISON: +// Top-tier avg: 11.31s, $0.005727, 100% quality +// Budget avg: 11.77s, $0.000384, 100% quality +// Budget is 14.9x cheaper on average + +// ======================================================================================================================== + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; +use std::time::{Duration, Instant}; + +/// Model configuration for benchmarking +struct ModelConfig { + /// Display name for the model + name: &'static str, + /// OpenRouter model identifier + model_id: &'static str, + /// Tier (top-tier or budget) + tier: &'static str, + /// Approximate cost per 1M input tokens (USD) + input_cost_per_m: f64, + /// Approximate cost per 1M output tokens (USD) + output_cost_per_m: f64, +} + +/// Benchmark result for a single model +struct BenchmarkResult { + model_name: String, + tier: String, + duration: Duration, + prompt_tokens: u32, + completion_tokens: u32, + total_tokens: u32, + estimated_cost: f64, + success: bool, + error: Option, + /// Quality score (0-100) based on expected fields present + quality_score: u8, + /// Fields found in the extraction + fields_found: Vec, +} + +/// Expected fields for the book extraction test +const EXPECTED_FIELDS: &[&str] = &["title", "price", "availability", "description", "upc"]; + +/// Validate the extraction output and return (quality_score, fields_found) +fn validate_extraction(content_str: &str) -> (u8, Vec) { + let mut fields_found = Vec::new(); + let content_lower = content_str.to_lowercase(); + + for field in EXPECTED_FIELDS { + if content_lower.contains(field) { + fields_found.push(field.to_string()); + } + } + + // Check for specific expected values + let has_title = content_lower.contains("light in the attic"); + let has_price = content_lower.contains("51.77"); + let has_upc = content_lower.contains("a897fe39b1053632"); + + let mut bonus = 0u8; + if has_title { + bonus += 10; + } + if has_price { + bonus += 10; + } + if has_upc { + bonus += 10; + } + + let base_score = (fields_found.len() as f64 / EXPECTED_FIELDS.len() as f64 * 70.0) as u8; + let quality_score = (base_score + bonus).min(100); + + (quality_score, fields_found) +} + +/// Get the list of models to benchmark (verified OpenRouter multimodal models) +fn get_models() -> Vec { + vec![ + // Top-tier models (highest quality) + ModelConfig { + name: "GPT-4o", + model_id: "openai/gpt-4o", + tier: "top-tier", + input_cost_per_m: 2.50, + output_cost_per_m: 10.00, + }, + ModelConfig { + name: "Claude 3.5 Sonnet", + model_id: "anthropic/claude-3.5-sonnet", + tier: "top-tier", + input_cost_per_m: 3.00, + output_cost_per_m: 15.00, + }, + ModelConfig { + name: "Gemini 2.0 Flash", + model_id: "google/gemini-2.0-flash-001", + tier: "top-tier", + input_cost_per_m: 0.10, + output_cost_per_m: 0.40, + }, + ModelConfig { + name: "Qwen2-VL 72B", + model_id: "qwen/qwen-2-vl-72b-instruct", + tier: "top-tier", + input_cost_per_m: 0.40, + output_cost_per_m: 0.40, + }, + // Budget models (fast and cheap) + ModelConfig { + name: "Gemini 2.0 Flash Lite", + model_id: "google/gemini-2.0-flash-lite-001", + tier: "budget", + input_cost_per_m: 0.075, + output_cost_per_m: 0.30, + }, + ModelConfig { + name: "Qwen2-VL 7B", + model_id: "qwen/qwen-2-vl-7b-instruct", + tier: "budget", + input_cost_per_m: 0.10, + output_cost_per_m: 0.10, + }, + ModelConfig { + name: "Pixtral 12B", + model_id: "mistralai/pixtral-12b", + tier: "budget", + input_cost_per_m: 0.10, + output_cost_per_m: 0.10, + }, + ] +} + +async fn benchmark_model(api_key: &str, url: &str, model: &ModelConfig) -> BenchmarkResult { + println!("\n[{}] Testing {}...", model.tier, model.name); + + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + model.model_id, + ); + + mm_config.api_key = Some(api_key.to_string()); + mm_config.system_prompt = Some( + "Extract the following data from the webpage content and return as valid JSON: \ + Extract book details including title, price, availability, description, and any other relevant information." + .to_string(), + ); + + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; + mm_config.cfg.request_json_object = true; + + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Use subscribe + crawl pattern (like the working example) + let mut rx = website.subscribe(16).unwrap(); + + // Collect results from the subscription + let results_handle = tokio::spawn(async move { + let mut prompt_tokens = 0u32; + let mut completion_tokens = 0u32; + let mut total_tokens = 0u32; + let mut success = false; + let mut error: Option = None; + let mut content_str: Option = None; + + while let Ok(page) = rx.recv().await { + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + for result in ai_data { + if let Some(ref usage) = result.usage { + prompt_tokens = usage.prompt_tokens; + completion_tokens = usage.completion_tokens; + total_tokens = usage.total_tokens; + } + if let Some(ref err) = result.error { + error = Some(err.clone()); + } else if !result.content_output.is_null() { + success = true; + content_str = Some(result.content_output.to_string()); + } + } + } else { + let html_len = page.get_html().len(); + if html_len > 0 && error.is_none() { + error = Some(format!("Page fetched ({}B) but no AI data", html_len)); + } + } + } + + ( + prompt_tokens, + completion_tokens, + total_tokens, + success, + error, + content_str, + ) + }); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + // Close the channel + website.unsubscribe(); + + // Get results from the spawned task + let (prompt_tokens, completion_tokens, total_tokens, success, error, content_output) = + results_handle + .await + .unwrap_or((0, 0, 0, false, Some("Task failed".to_string()), None)); + + // Validate extraction quality + let (quality_score, fields_found) = if let Some(ref content) = content_output { + validate_extraction(content) + } else { + (0, vec![]) + }; + + // Calculate estimated cost + let estimated_cost = (prompt_tokens as f64 * model.input_cost_per_m / 1_000_000.0) + + (completion_tokens as f64 * model.output_cost_per_m / 1_000_000.0); + + let status_icon = if success && quality_score >= 70 { + "✓" + } else if success { + "~" + } else { + "✗" + }; + println!( + " {} Completed in {:.2}s | Tokens: {} | Quality: {}% | Est. cost: ${:.6}", + status_icon, + duration.as_secs_f64(), + total_tokens, + quality_score, + estimated_cost + ); + + BenchmarkResult { + model_name: model.name.to_string(), + tier: model.tier.to_string(), + duration, + prompt_tokens, + completion_tokens, + total_tokens, + estimated_cost, + success: success && quality_score >= 70, + error, + quality_score, + fields_found, + } +} + +fn print_results_table(results: &[BenchmarkResult]) { + println!("\n{}", "=".repeat(130)); + println!("BENCHMARK RESULTS"); + println!("{}", "=".repeat(130)); + + println!( + "\n{:<22} {:<10} {:>8} {:>10} {:>10} {:>10} {:>10} {:>8} {:>8}", + "Model", "Tier", "Time(s)", "Prompt", "Comp", "Total", "Cost($)", "Quality", "Status" + ); + println!("{}", "-".repeat(130)); + + for result in results { + let status = if result.success { "✓" } else { "✗" }; + let quality_str = format!("{}%", result.quality_score); + println!( + "{:<22} {:<10} {:>8.2} {:>10} {:>10} {:>10} {:>10.6} {:>8} {:>8}", + result.model_name, + result.tier, + result.duration.as_secs_f64(), + result.prompt_tokens, + result.completion_tokens, + result.total_tokens, + result.estimated_cost, + quality_str, + status + ); + if let Some(ref err) = result.error { + println!(" └─ Error: {}", err); + } + if result.quality_score < 100 && !result.fields_found.is_empty() { + println!(" └─ Fields found: {}", result.fields_found.join(", ")); + } + } + + println!("{}", "-".repeat(130)); + + // Summary statistics + let successful: Vec<_> = results.iter().filter(|r| r.success).collect(); + if !successful.is_empty() { + println!("\nSUMMARY (successful runs only):"); + + // Fastest model + if let Some(fastest) = successful.iter().min_by_key(|r| r.duration) { + println!( + " 🏆 Fastest: {} ({:.2}s)", + fastest.model_name, + fastest.duration.as_secs_f64() + ); + } + + // Cheapest model + if let Some(cheapest) = successful + .iter() + .filter(|r| r.estimated_cost > 0.0) + .min_by(|a, b| a.estimated_cost.partial_cmp(&b.estimated_cost).unwrap()) + { + println!( + " 💰 Cheapest: {} (${:.6})", + cheapest.model_name, cheapest.estimated_cost + ); + } + + // Most efficient (tokens per second) + if let Some(efficient) = successful + .iter() + .filter(|r| r.duration.as_secs_f64() > 0.0) + .max_by(|a, b| { + let a_tps = a.completion_tokens as f64 / a.duration.as_secs_f64(); + let b_tps = b.completion_tokens as f64 / b.duration.as_secs_f64(); + a_tps.partial_cmp(&b_tps).unwrap() + }) + { + let tps = efficient.completion_tokens as f64 / efficient.duration.as_secs_f64(); + println!( + " ⚡ Highest throughput: {} ({:.1} tokens/s)", + efficient.model_name, tps + ); + } + + // Best value (quality per dollar) + if let Some(best_value) = + successful + .iter() + .filter(|r| r.estimated_cost > 0.0) + .max_by(|a, b| { + let a_val = a.quality_score as f64 / a.estimated_cost; + let b_val = b.quality_score as f64 / b.estimated_cost; + a_val.partial_cmp(&b_val).unwrap() + }) + { + println!( + " 🎯 Best value: {} ({}% quality, ${:.6})", + best_value.model_name, best_value.quality_score, best_value.estimated_cost + ); + } + + // Highest quality + if let Some(best_quality) = successful.iter().max_by_key(|r| r.quality_score) { + println!( + " 🌟 Highest quality: {} ({}%)", + best_quality.model_name, best_quality.quality_score + ); + } + } + + // Top-tier vs Budget comparison + let top_tier: Vec<_> = successful.iter().filter(|r| r.tier == "top-tier").collect(); + let budget: Vec<_> = successful.iter().filter(|r| r.tier == "budget").collect(); + + if !top_tier.is_empty() && !budget.is_empty() { + let avg_top_time: f64 = top_tier + .iter() + .map(|r| r.duration.as_secs_f64()) + .sum::() + / top_tier.len() as f64; + let avg_budget_time: f64 = + budget.iter().map(|r| r.duration.as_secs_f64()).sum::() / budget.len() as f64; + let avg_top_cost: f64 = + top_tier.iter().map(|r| r.estimated_cost).sum::() / top_tier.len() as f64; + let avg_budget_cost: f64 = + budget.iter().map(|r| r.estimated_cost).sum::() / budget.len() as f64; + let avg_top_quality: f64 = + top_tier.iter().map(|r| r.quality_score as f64).sum::() / top_tier.len() as f64; + let avg_budget_quality: f64 = + budget.iter().map(|r| r.quality_score as f64).sum::() / budget.len() as f64; + + println!("\nTIER COMPARISON:"); + println!( + " Top-tier avg: {:.2}s, ${:.6}, {:.0}% quality", + avg_top_time, avg_top_cost, avg_top_quality + ); + println!( + " Budget avg: {:.2}s, ${:.6}, {:.0}% quality", + avg_budget_time, avg_budget_cost, avg_budget_quality + ); + if avg_budget_cost > 0.0 { + println!( + " Budget is {:.1}x cheaper on average", + avg_top_cost / avg_budget_cost + ); + } + } + + println!("\n{}", "=".repeat(120)); +} + +#[tokio::main] +async fn main() { + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + let url = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"; + let models = get_models(); + + println!("Remote Multimodal Model Benchmark"); + println!("=================================="); + println!("URL: {}", url); + println!("Models to test: {}", models.len()); + println!("\nStarting benchmark..."); + + let mut results = Vec::new(); + + for model in &models { + let result = benchmark_model(&api_key, url, model).await; + results.push(result); + + // Small delay between requests to avoid rate limiting + tokio::time::sleep(Duration::from_millis(500)).await; + } + + print_results_table(&results); +} diff --git a/examples/remote_multimodal_dual.rs b/examples/remote_multimodal_dual.rs new file mode 100644 index 000000000..c25d18a23 --- /dev/null +++ b/examples/remote_multimodal_dual.rs @@ -0,0 +1,142 @@ +//! Dual-model routing example with separate endpoints. +//! +//! Demonstrates vision + text model routing where each model can have its own +//! API URL and API key. A cheap text model handles most rounds while a +//! vision model kicks in for round 0 and stagnation recovery. +//! +//! This example shows two setups: +//! 1. Same provider (both on OpenRouter, different models) +//! 2. Cross-provider (vision on OpenAI, text on OpenRouter) +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_dual --features "spider/sync spider/chrome spider/agent_chrome" +//! ``` +//! +//! EXAMPLE output +//! === Page Received === +//! URL: https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html +//! +//! === AI Extraction Results === +//! Result 1: +//! Content: {"title":"A Light in the Attic","price":"£51.77","availability":"In stock (22 available)","upc":"a897fe39b1053632"} +//! Usage: AutomationUsage { prompt_tokens: 2860, completion_tokens: 168, total_tokens: 3028, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } +//! +//! === Completed in 2.48s === + +extern crate spider; + +use spider::features::automation::{ModelEndpoint, RemoteMultimodalConfigs, VisionRouteMode}; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + let url = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"; + + // ── Setup 1: Same provider, different models ────────────────────── + // + // Both models share the parent API URL and key from RemoteMultimodalConfigs. + // Only the model_name differs per endpoint. + // + // let mm_config = RemoteMultimodalConfigs::new( + // "https://openrouter.ai/api/v1/chat/completions", + // "qwen/qwen-2.5-vl-72b-instruct", + // ) + // .with_api_key(&api_key) + // .with_dual_models( + // ModelEndpoint::new("qwen/qwen-2.5-vl-72b-instruct"), // vision + // ModelEndpoint::new("qwen/qwen-2.5-72b-instruct"), // text (cheaper) + // ) + // .with_vision_route_mode(VisionRouteMode::TextFirst); + + // ── Setup 2: Cross-provider, each model has its own URL + key ───── + // + // Vision model → OpenRouter (Qwen VL, vision-capable) + // Text model → OpenRouter (different model, cheaper) + // + // Each ModelEndpoint can override api_url and api_key independently. + // Fields left as None inherit from the parent RemoteMultimodalConfigs. + + let mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", // default/fallback URL + "qwen/qwen-2.5-vl-72b-instruct", // default/fallback model + ) + .with_api_key(&api_key) + .with_vision_model( + // Vision model: explicit URL + key (or omit to inherit from parent) + ModelEndpoint::new("qwen/qwen-2.5-vl-72b-instruct") + .with_api_url("https://openrouter.ai/api/v1/chat/completions") + .with_api_key(&api_key), + ) + .with_text_model( + // Text model: can point to a completely different provider + // Here we use OpenRouter too, but you could use Groq, Together, etc: + // .with_api_url("https://api.groq.com/openai/v1/chat/completions") + // .with_api_key("gsk-your-groq-key") + ModelEndpoint::new("qwen/qwen-2.5-72b-instruct") + .with_api_url("https://openrouter.ai/api/v1/chat/completions") + .with_api_key(&api_key), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + // VisionRouteMode::TextFirst means: + // - Round 0: vision model (screenshot + HTML) + // - Subsequent rounds: text model (HTML only, no screenshot → saves ~35k tokens) + // - Stagnation or stuck ≥ 3: upgrade back to vision model + + // ── Configure extraction settings ───────────────────────────────── + let mut mm_config = mm_config; + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; + mm_config.cfg.request_json_object = true; + mm_config.cfg.extraction_prompt = + Some("Extract book details: title, price, availability, UPC, description.".to_string()); + + // ── Run ─────────────────────────────────────────────────────────── + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + let mut rx = website.subscribe(16).unwrap(); + + let join_handle = tokio::spawn(async move { + while let Ok(page) = rx.recv().await { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Content: {}", result.content_output); + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + } + } else { + println!("\nNo AI extraction data available."); + } + } + }); + + let start = tokio::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let _ = join_handle.await; + + println!("\n=== Completed in {:?} ===", start.elapsed()); +} diff --git a/examples/remote_multimodal_dual_automation.rs b/examples/remote_multimodal_dual_automation.rs new file mode 100644 index 000000000..581a57437 --- /dev/null +++ b/examples/remote_multimodal_dual_automation.rs @@ -0,0 +1,122 @@ +//! Dual-model multi-round automation example. +//! +//! Shows dual-model routing for multi-round browser automation (not just +//! extraction). A vision model handles the first round and stagnation, +//! while a cheaper text model drives mid-round actions via HTML context. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_dual_automation --features "spider/sync spider/chrome spider/agent_chrome" +//! ``` +//! +//! EXAMPLE output +//! === Page Received === +//! URL: https://books.toscrape.com/ +//! +//! === AI Results === +//! Result 1: +//! Content: {"extracted":{"title":"A Light in the Attic","price":"£51.77","availability":"In stock (22 available)"},"steps":[{"Click":{"selector":"article.product_pod h3 a"}}]} +//! Tokens: 3541 prompt + 296 completion = 3837 total (2 LLM calls) +//! +//! === Completed in 5.31s === + +extern crate spider; + +use spider::features::automation::{ModelEndpoint, RemoteMultimodalConfigs, VisionRouteMode}; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + // A page that requires multi-round interaction + let url = "https://books.toscrape.com/"; + + // ── Dual-model routing for automation ───────────────────────────── + // + // VisionFirst: vision model for rounds 0-1 (screenshot), then text model + // for stable mid-rounds, upgrading back to vision on stagnation/stuck. + // + // You can also use different providers for each model by setting + // `api_url` and `api_key` on the ModelEndpoint: + // + // ModelEndpoint::new("gpt-4o") + // .with_api_url("https://api.openai.com/v1/chat/completions") + // .with_api_key("sk-openai-...") + // + // Fields left as None inherit from the parent RemoteMultimodalConfigs. + + let mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2.5-vl-72b-instruct", + ) + .with_api_key(&api_key) + .with_dual_models( + ModelEndpoint::new("qwen/qwen-2.5-vl-72b-instruct"), // vision rounds + ModelEndpoint::new("qwen/qwen-2.5-72b-instruct"), // text rounds + ) + .with_vision_route_mode(VisionRouteMode::VisionFirst); + + // ── Multi-round automation config ───────────────────────────────── + let mut mm_config = mm_config; + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 4; // Multi-round: full automation prompt + mm_config.cfg.request_json_object = true; + mm_config.cfg.extraction_prompt = Some( + "Navigate to the first book in the catalog. Extract its title, price, and availability." + .to_string(), + ); + + // Optional: set the user instruction for the automation task + mm_config.user_message_extra = Some( + "Click on the first book link to navigate to its detail page, then extract the book data." + .to_string(), + ); + + // ── Run ─────────────────────────────────────────────────────────── + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + let mut rx = website.subscribe(16).unwrap(); + + let join_handle = tokio::spawn(async move { + while let Ok(page) = rx.recv().await { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Content: {}", result.content_output); + if let Some(ref usage) = result.usage { + println!( + " Tokens: {} prompt + {} completion = {} total ({} LLM calls)", + usage.prompt_tokens, + usage.completion_tokens, + usage.total_tokens, + usage.llm_calls, + ); + } + } + } + } + }); + + let start = tokio::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let _ = join_handle.await; + + println!("\n=== Completed in {:?} ===", start.elapsed()); +} diff --git a/examples/remote_multimodal_listing.rs b/examples/remote_multimodal_listing.rs new file mode 100644 index 000000000..3e85e0cd4 --- /dev/null +++ b/examples/remote_multimodal_listing.rs @@ -0,0 +1,82 @@ +//! Remote multimodal extraction example for listing pages. +//! +//! This example extracts a compact product list (title, price, availability) +//! from a category/listing page. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_listing --features "spider/sync spider/chrome spider/agent_chrome" +//! ``` +//! +//! EXAMPLE output +//! === Page Received === +//! URL: https://books.toscrape.com/catalogue/category/books/travel_2/index.html +//! +//! === AI Extraction Results === +//! Result 1: +//! Input prompt: extract_listing_books +//! Content output: {"category":"Travel","products":[{"title":"It's Only the Himalayas","price":"£45.17","availability":"In stock"}]} +//! Usage: AutomationUsage { prompt_tokens: 2874, completion_tokens: 220, total_tokens: 3094, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + let url = "https://books.toscrape.com/catalogue/category/books/travel_2/index.html"; + + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", + ); + mm_config.api_key = Some(api_key); + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; + mm_config.cfg.request_json_object = true; + mm_config.cfg.extraction_prompt = Some( + "extract_listing_books: return JSON with `category` and `products`. \ + `products` should contain up to 8 entries with `title`, `price`, and `availability`." + .to_string(), + ); + + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + website.scrape().await; + + if let Some(pages) = website.get_pages() { + for page in pages { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Input prompt: {}", result.input); + println!(" Content output: {}", result.content_output); + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + } + } else { + println!("\nNo AI extraction data available."); + } + } + } +} diff --git a/examples/remote_multimodal_multi.rs b/examples/remote_multimodal_multi.rs new file mode 100644 index 000000000..0974b3c53 --- /dev/null +++ b/examples/remote_multimodal_multi.rs @@ -0,0 +1,143 @@ +//! Remote multimodal automation example with multi-page extraction. +//! +//! This example demonstrates how to use a remote multimodal model (via OpenRouter) +//! to extract structured data from multiple pages by starting from a category page +//! and following links to individual book detail pages. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_multi --features "spider/sync spider/chrome spider/agent_chrome" +//! ``` + +// EXAMPLE output +// === Page Received === +// URL: https://books.toscrape.com/catalogue/shakespeares-sonnets_989/index.html +// === AI Extraction Results === +// Result 1: +// Input prompt: extract_book_details +// Content output: {"book":{"title":"Shakespeare's Sonnets","price":"£20.66",...}} +// +// === Page Received === +// URL: https://books.toscrape.com/catalogue/our-band-could-be-your-life-scenes-from-the-american-indie-underground-1981-1991_985/index.html +// === AI Extraction Results === +// Result 1: +// Input prompt: extract_book_details +// Content output: {"book":{"title":"Our Band Could Be Your Life",...}} + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + // Get API key from environment variable + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + // Start from the poetry category page - will follow links to individual books + let url = "https://books.toscrape.com/catalogue/category/books/poetry_23/index.html"; + + // Configure remote multimodal with OpenRouter + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", // OpenRouter model identifier + ); + + // Set the API key + mm_config.api_key = Some(api_key); + + // Set up extraction prompt + mm_config.system_prompt = Some( + "Extract the following data from the webpage content and return as valid JSON: \ + Extract book details including title, price, availability, description, and any other relevant information. \ + If this is a category/listing page rather than a book detail page, return {\"page_type\": \"category\"}." + .to_string(), + ); + + // Configure extraction settings + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; // Single extraction pass + mm_config.cfg.request_json_object = true; + + // Create website with remote multimodal config + // - Start from category page + // - depth(2) to follow links from category to book pages + // - limit(3) to process category page + 2 book detail pages + let mut website: Website = Website::new(url) + .with_depth(2) + .with_limit(3) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Subscribe to receive pages + let mut rx = website.subscribe(16).unwrap(); + + // Spawn task to handle received pages + let join_handle = tokio::spawn(async move { + let mut book_count = 0; + while let Ok(page) = rx.recv().await { + let url = page.get_url(); + println!("\n=== Page Received ==="); + println!("URL: {}", url); + + // Check if this is a book detail page (not a category page) + let is_book_page = !url.contains("/category/"); + + if is_book_page { + book_count += 1; + } + + // Check for remote multimodal extraction results + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Input prompt: {}", result.input); + println!(" Content output: {}", result.content_output); + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + } + } else { + println!("\nNo AI extraction data available."); + } + + // Check for remote multimodal usage statistics + if let Some(ref usage) = page.remote_multimodal_usage { + println!("\n=== Remote Multimodal Usage ==="); + for (i, u) in usage.iter().enumerate() { + println!("Usage {}: {:?}", i + 1, u); + } + } + + println!("\n--- Book pages extracted: {} ---", book_count); + } + }); + + // Start crawling + let start = tokio::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + // Unsubscribe to close the channel and let the spawned task exit + website.unsubscribe(); + + // Wait for the spawned task to complete + let _ = join_handle.await; + + println!("\n=== Completed ==="); + println!("Time elapsed: {:?}", duration); + println!( + "Total pages visited: {:?}", + website.get_all_links_visited().await.len() + ); +} diff --git a/examples/remote_multimodal_quotes.rs b/examples/remote_multimodal_quotes.rs new file mode 100644 index 000000000..013013536 --- /dev/null +++ b/examples/remote_multimodal_quotes.rs @@ -0,0 +1,82 @@ +//! Remote multimodal extraction example for quotes. +//! +//! This example extracts structured quote data (quote text, author, tags) +//! from a page and prints model usage metrics. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_quotes --features "spider/sync spider/chrome spider/agent_chrome" +//! ``` +//! +//! EXAMPLE output +//! === Page Received === +//! URL: https://quotes.toscrape.com/ +//! +//! === AI Extraction Results === +//! Result 1: +//! Input prompt: extract_quotes +//! Content output: {"quotes":[{"text":"The world as we have created it is a process of our thinking.","author":"Albert Einstein","tags":["change","deep-thoughts","thinking","world"]}]} +//! Usage: AutomationUsage { prompt_tokens: 2410, completion_tokens: 196, total_tokens: 2606, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + let url = "https://quotes.toscrape.com/"; + + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", + ); + mm_config.api_key = Some(api_key); + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; + mm_config.cfg.request_json_object = true; + mm_config.cfg.extraction_prompt = Some( + "extract_quotes: return JSON with key `quotes` containing up to 5 entries. \ + Each entry should include `text`, `author`, and `tags`." + .to_string(), + ); + + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + website.scrape().await; + + if let Some(pages) = website.get_pages() { + for page in pages { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Input prompt: {}", result.input); + println!(" Content output: {}", result.content_output); + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + } + } else { + println!("\nNo AI extraction data available."); + } + } + } +} diff --git a/examples/remote_multimodal_scrape.rs b/examples/remote_multimodal_scrape.rs new file mode 100644 index 000000000..5fb530258 --- /dev/null +++ b/examples/remote_multimodal_scrape.rs @@ -0,0 +1,119 @@ +//! Remote multimodal automation example using OpenRouter. +//! +//! This example demonstrates how to use a remote multimodal model (via OpenRouter) +//! to extract structured data from web pages. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example remote_multimodal_scrape --features "spider/sync spider/chrome" +//! ``` + +// EXAMPLE output +// === Page Received === +// URL: https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html + +// === AI Extraction Results === +// Result 1: +// Input prompt: extract_book_details +// Content output: {"book":{"availability":"In stock (22 available)","description":"It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary with this special edition. Silverstein's humorous and creative verse can amuse the dowdiest of readers. Lemon-faced adults and fidgety kids sit still and read these rhythmic words and laugh and smile and love that Silverstein. Need proof of his genius? RockabyeRockabye baby, in the treetopDon't you know a treetopIs no safe place to rock?And who put you up there,And your cradle, too?Baby, I think someone down here'sGot it in for you. Shel, you never sounded so good.","number_of_reviews":"0","price":"£51.77","price_excl_tax":"£51.77","price_incl_tax":"£51.77","product_type":"Books","tax":"£0.00","title":"A Light in the Attic","upc":"a897fe39b1053632"}} +// Usage: AutomationUsage { prompt_tokens: 3223, completion_tokens: 312, total_tokens: 3535, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +// === Remote Multimodal Usage === +// Usage 1: AutomationUsage { prompt_tokens: 3223, completion_tokens: 312, total_tokens: 3535, llm_calls: 1, search_calls: 0, fetch_calls: 0, webbrowser_calls: 0, custom_tool_calls: {}, api_calls: 1 } + +extern crate spider; + +use spider::features::automation::RemoteMultimodalConfigs; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + // Get API key from environment variable + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + // Target URL - a book details page + let url = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"; + + // Configure remote multimodal with OpenRouter + let mut mm_config = RemoteMultimodalConfigs::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", // OpenRouter model identifier + ); + + // Set the API key + mm_config.api_key = Some(api_key); + + // Set up extraction prompt + mm_config.system_prompt = Some( + "Extract the following data from the webpage content and return as valid JSON: \ + Extract book details including title, price, availability, description, and any other relevant information." + .to_string(), + ); + + // Configure extraction settings + mm_config.cfg.extra_ai_data = true; + mm_config.cfg.include_html = true; + mm_config.cfg.include_title = true; + mm_config.cfg.include_url = true; + mm_config.cfg.max_rounds = 1; // Single extraction pass + mm_config.cfg.request_json_object = true; + + // Create website with remote multimodal config + let mut website: Website = Website::new(url) + .with_limit(1) + .with_remote_multimodal(Some(mm_config)) + .build() + .unwrap(); + + // Start scraping - scrape() handles subscription internally and collects pages + let start = tokio::time::Instant::now(); + website.scrape().await; + let duration = start.elapsed(); + + // Access collected pages after scrape completes + if let Some(pages) = website.get_pages() { + for page in pages { + println!("=== Page Received ==="); + println!("URL: {}", page.get_url()); + + // Check for remote multimodal extraction results + if let Some(ref ai_data) = page.extra_remote_multimodal_data { + println!("\n=== AI Extraction Results ==="); + for (i, result) in ai_data.iter().enumerate() { + println!("Result {}:", i + 1); + println!(" Input prompt: {}", result.input); + println!(" Content output: {}", result.content_output); + if let Some(ref err) = result.error { + println!(" Error: {}", err); + } + if let Some(ref usage) = result.usage { + println!(" Usage: {:?}", usage); + } + } + } else { + println!("\nNo AI extraction data available."); + } + + // Check for remote multimodal usage statistics + if let Some(ref usage) = page.remote_multimodal_usage { + println!("\n=== Remote Multimodal Usage ==="); + for (i, u) in usage.iter().enumerate() { + println!("Usage {}: {:?}", i + 1, u); + } + } + + println!("\n=== Raw HTML (truncated) ==="); + let html = page.get_html(); + if html.len() > 500 { + println!("{}...", &html[..500]); + } else { + println!("{}", html); + } + } + } + + println!("\n=== Completed ==="); + println!("Time elapsed: {:?}", duration); +} diff --git a/examples/rss.rs b/examples/rss.rs new file mode 100644 index 000000000..6bdd8c109 --- /dev/null +++ b/examples/rss.rs @@ -0,0 +1,36 @@ +//! `cargo run --example rss` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://a11ywatch.com/rss") + .with_limit(5) + .build() + .unwrap(); + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(16).unwrap(); + + tokio::spawn(async move { + let mut stdout = tokio::io::stdout(); + + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + }); + + let start = std::time::Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_links().len() + ) +} diff --git a/examples/scrape.rs b/examples/scrape.rs new file mode 100644 index 000000000..1b6924f93 --- /dev/null +++ b/examples/scrape.rs @@ -0,0 +1,41 @@ +//! `cargo run --example scrape` +extern crate env_logger; +extern crate spider; + +use env_logger::Env; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + use std::io::{stdout, Write}; + + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + let target = "https://spider.cloud"; + let mut website: Website = Website::new(target); + website.configuration.respect_robots_txt = true; + website.configuration.delay = 15; // Defaults to 250 ms + website.configuration.user_agent = Some(Box::new("SpiderBot".into())); // Defaults to spider/x.y.z, where x.y.z is the library version + + website.scrape().await; + + let mut lock = stdout().lock(); + + let separator = "-".repeat(target.len()); + + for page in website.get_pages().unwrap().iter() { + writeln!( + lock, + "{}\n{}\n\n{}\n\n{}", + separator, + page.get_url(), + page.get_html(), + separator + ) + .unwrap(); + } +} diff --git a/examples/serde.rs b/examples/serde.rs new file mode 100644 index 000000000..64deeb83e --- /dev/null +++ b/examples/serde.rs @@ -0,0 +1,22 @@ +//! `cargo run --example serde --features serde` +extern crate flexbuffers; +extern crate spider; + +use spider::serde::ser::Serialize; +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + + website.crawl().await; + + let links = website.get_all_links_visited().await; + + let mut s = flexbuffers::FlexbufferSerializer::new(); + + links.serialize(&mut s).unwrap(); + + println!("{:?}", s) +} diff --git a/examples/sitemap.rs b/examples/sitemap.rs new file mode 100644 index 000000000..47301459c --- /dev/null +++ b/examples/sitemap.rs @@ -0,0 +1,41 @@ +//! `cargo run --example sitemap --features sitemap` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::time::Instant; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en"); + + website + .configuration + .with_respect_robots_txt(true) + .with_user_agent(Some("SpiderBot")) + .with_ignore_sitemap(true) // ignore running the sitemap on base crawl/scape methods. Remove or set to true to include the sitemap with the crawl. + .with_sitemap(Some("/sitemap/sitemap-0.xml")); + + let start = Instant::now(); + + // crawl the sitemap first + website.crawl_sitemap().await; + // persist links to the next crawl + website.persist_links(); + // crawl normal with links found in the sitemap extended. + website.crawl().await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/sitemap_only.rs b/examples/sitemap_only.rs new file mode 100644 index 000000000..7847eb8bc --- /dev/null +++ b/examples/sitemap_only.rs @@ -0,0 +1,34 @@ +//! `cargo run --example sitemap_only --features sitemap` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::time::Instant; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://spider.cloud"); + + website + .configuration + .with_respect_robots_txt(true) + .with_user_agent(Some("SpiderBot")); + + let start = Instant::now(); + + website.crawl_sitemap().await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/smart.rs b/examples/smart.rs new file mode 100644 index 000000000..2c5bc8587 --- /dev/null +++ b/examples/smart.rs @@ -0,0 +1,21 @@ +//! `cargo run --example smart --features smart` +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://choosealicense.com"); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + } + }); + + website.crawl_smart().await; + + println!("Links found {:?}", website.get_size().await); +} diff --git a/examples/subscribe.rs b/examples/subscribe.rs new file mode 100644 index 000000000..8f2d63d2b --- /dev/null +++ b/examples/subscribe.rs @@ -0,0 +1,41 @@ +//! `cargo run --example subscribe` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://spider.cloud"); + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + let mut stdout = tokio::io::stdout(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + stdout + }); + + let start = std::time::Instant::now(); + + website.crawl().await; + website.unsubscribe(); + let duration = start.elapsed(); + let mut stdout = join_handle.await.unwrap(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_size().await + ) + .as_bytes(), + ) + .await; +} diff --git a/examples/subscribe_download.rs b/examples/subscribe_download.rs new file mode 100644 index 000000000..7abe1e284 --- /dev/null +++ b/examples/subscribe_download.rs @@ -0,0 +1,60 @@ +//! `cargo run --example subscribe_download` +extern crate env_logger; +extern crate spider; + +use spider::percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use spider::utils::log; +use spider::website::Website; + +use env_logger::Env; +use spider::tokio; +use tokio::fs::OpenOptions; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + // view the target dist for the downloads + std::fs::create_dir_all("./target/downloads").unwrap_or_default(); + + let env = Env::default() + .filter_or("RUST_LOG", "info") + .write_style_or("RUST_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + + let website_name = "https://rsseau.fr/en"; + + let mut website: Website = Website::new(website_name); + let mut rx2 = website.subscribe(888).unwrap(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + let download_file = + percent_encode(page.get_url().as_bytes(), NON_ALPHANUMERIC).to_string(); + + let download_file = if download_file.is_empty() { + "index" + } else { + &download_file + }; + + let download_file = format!("./target/downloads/{}.html", download_file); + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&download_file) + .await + .expect("Unable to open file"); + + if let Some(b) = page.get_bytes() { + file.write_all(b).await.unwrap_or_default(); + } + + log("downloaded", download_file) + } + }); + + website.crawl().await; +} diff --git a/examples/subscribe_multiple.rs b/examples/subscribe_multiple.rs new file mode 100644 index 000000000..bd88ca7d0 --- /dev/null +++ b/examples/subscribe_multiple.rs @@ -0,0 +1,56 @@ +//! `cargo run --example subscribe_multiple` +extern crate spider; + +use spider::{tokio, website::Website}; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://example.com?target=1"); + + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + + let mut website2 = website.clone(); + website2.set_url_only("https://example.com?target=2"); + // usually you want to use another proxy. + // website2.with_proxies(Some(vec!["http://myproxy.com"])); + + let mut stdout = tokio::io::stdout(); + + let sub = async move { + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + stdout + }; + + let start = std::time::Instant::now(); + + let c1 = async { + website.crawl().await; + website.unsubscribe(); + }; + let c2 = async { + website2.crawl().await; + website2.unsubscribe(); + }; + + // you can also use a select to cancel a crawl if you want to see which proxy comes first. + let (mut stdout, _crawl_one, _crawl_two) = tokio::join!(sub, c1, c2); + + let duration = start.elapsed(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() and website2.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_size().await + website2.get_size().await + ) + .as_bytes(), + ) + .await; +} diff --git a/examples/transform_markdown.rs b/examples/transform_markdown.rs new file mode 100644 index 000000000..f4e1e40bf --- /dev/null +++ b/examples/transform_markdown.rs @@ -0,0 +1,48 @@ +// //! `cargo run --example transform_markdown --features="spider/sync"` +// extern crate spider; + +// use spider::tokio; +// use spider::website::Website; +// use spider_transformations::transformation::content::{ +// transform_content, ReturnFormat, TransformConfig, +// }; +// use tokio::io::AsyncWriteExt; + +// #[tokio::main] +// async fn main() { +// let mut website: Website = Website::new("https://rsseau.fr/en"); +// let mut rx2: tokio::sync::broadcast::Receiver = +// website.subscribe(0).unwrap(); +// let mut stdout = tokio::io::stdout(); + +// let mut conf = TransformConfig::default(); +// conf.return_format = ReturnFormat::Markdown; + +// let join_handle = tokio::spawn(async move { +// while let Ok(res) = rx2.recv().await { +// let markup = transform_content(&res, &conf, &None, &None, &None); + +// let _ = stdout +// .write_all(format!("- {}\n {}\n", res.get_url(), markup).as_bytes()) +// .await; +// } +// stdout +// }); + +// let start = std::time::Instant::now(); +// website.crawl().await; +// website.unsubscribe(); +// let duration = start.elapsed(); +// let mut stdout = join_handle.await.unwrap(); + +// let _ = stdout +// .write_all( +// format!( +// "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", +// duration, +// website.get_size().await +// ) +// .as_bytes(), +// ) +// .await; +// } diff --git a/examples/url_glob.rs b/examples/url_glob.rs new file mode 100644 index 000000000..0d33e161a --- /dev/null +++ b/examples/url_glob.rs @@ -0,0 +1,34 @@ +//! cargo run --example url_glob --features glob +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::time::Instant; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new( + "https://rsseau.fr/blog/{lazy-load-components,gnu-parallel,migrate-from-jekyll-to-gatsby}", + ); + website + .configuration + .blacklist_url + .insert(Default::default()) + .push("https://rsseau.fr/resume".into()); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_links(); + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/url_glob_subdomains.rs b/examples/url_glob_subdomains.rs new file mode 100644 index 000000000..20df4d348 --- /dev/null +++ b/examples/url_glob_subdomains.rs @@ -0,0 +1,30 @@ +//! `cargo run --example url_glob_subdomains --features glob` +extern crate spider; + +use spider::tokio; +use spider::website::Website; +use std::time::Instant; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://{www,docs}.a11ywatch.com") + .with_subdomains(true) + .build() + .unwrap(); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + for link in links.iter() { + println!("- {:?}", link.as_ref()); + } + + println!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + links.len() + ) +} diff --git a/examples/webdriver.rs b/examples/webdriver.rs new file mode 100644 index 000000000..ed8566231 --- /dev/null +++ b/examples/webdriver.rs @@ -0,0 +1,65 @@ +//! cargo run --example webdriver --features="webdriver webdriver_stealth" +//! +//! This example demonstrates basic WebDriver usage with spider. +//! You need to have a WebDriver server running (e.g., chromedriver, geckodriver, or Selenium). +//! +//! To start chromedriver: `chromedriver --port=4444` +//! To start geckodriver: `geckodriver --port=4444` + +extern crate spider; + +use spider::features::webdriver_common::{WebDriverBrowser, WebDriverConfig}; +use spider::tokio; +use spider::website::Website; +use std::io::Result; + +async fn crawl_website(url: &str) -> Result<()> { + let webdriver_config = WebDriverConfig::new() + .with_server_url("http://localhost:4444") + .with_browser(WebDriverBrowser::Chrome) + .with_headless(true); + + let mut website: Website = Website::new(url) + .with_limit(10) + .with_webdriver(webdriver_config) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + + let handle = tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + println!("{:?}", page.get_url()); + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let _ = handle.await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "Time elapsed in website.crawl({url}) is: {:?} for total pages: {:?}", + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://example.com"), + ); + + Ok(()) +} diff --git a/examples/webdriver_remote.rs b/examples/webdriver_remote.rs new file mode 100644 index 000000000..c645c36ca --- /dev/null +++ b/examples/webdriver_remote.rs @@ -0,0 +1,86 @@ +//! cargo run --example webdriver_remote --features="webdriver webdriver_stealth" +//! +//! This example demonstrates connecting to a remote WebDriver server like Selenium Grid. +//! +//! To start Selenium Grid with Docker: +//! ``` +//! docker run -d -p 4444:4444 --shm-size="2g" selenium/standalone-chrome:latest +//! ``` +//! +//! For Firefox: +//! ``` +//! docker run -d -p 4444:4444 --shm-size="2g" selenium/standalone-firefox:latest +//! ``` + +extern crate spider; + +use crate::spider::tokio::io::AsyncWriteExt; +use spider::features::webdriver_common::{WebDriverBrowser, WebDriverConfig}; +use spider::tokio; +use spider::website::Website; +use std::io::Result; +use std::time::Duration; + +async fn crawl_website(url: &str) -> Result<()> { + // Configure WebDriver to connect to remote Selenium Grid + let webdriver_config = WebDriverConfig::new() + .with_server_url("http://localhost:4444") + .with_browser(WebDriverBrowser::Chrome) + .with_headless(true) + .with_timeout(Duration::from_secs(30)) + .with_viewport(1920, 1080); + + let mut website: Website = Website::new(url) + .with_limit(50) + .with_webdriver(webdriver_config) + .build() + .unwrap(); + + let mut rx2 = website.subscribe(16).unwrap(); + let mut stdout = tokio::io::stdout(); + + tokio::spawn(async move { + while let Ok(page) = rx2.recv().await { + let _ = stdout + .write_all( + format!( + "- {} -- Status: {} -- HTML Size {:?}\n", + page.get_url(), + page.status_code, + page.get_html_bytes_u8().len() + ) + .as_bytes(), + ) + .await; + } + }); + + let start = crate::tokio::time::Instant::now(); + website.crawl().await; + + let duration = start.elapsed(); + + let links = website.get_all_links_visited().await; + + println!( + "\nTime elapsed in website.crawl({}) is: {:?} for total pages: {:?}", + website.get_url(), + duration, + links.len() + ); + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let _ = tokio::join!( + crawl_website("https://choosealicense.com"), + crawl_website("https://jeffmendez.com"), + crawl_website("https://example.com"), + ); + + Ok(()) +} diff --git a/examples/webdriver_screenshot.rs b/examples/webdriver_screenshot.rs new file mode 100644 index 000000000..af4f51327 --- /dev/null +++ b/examples/webdriver_screenshot.rs @@ -0,0 +1,86 @@ +//! cargo run --example webdriver_screenshot --features="webdriver webdriver_screenshot" +//! +//! This example demonstrates taking screenshots using WebDriver. +//! Screenshots are saved as PNG files in the current directory. +//! +//! You need to have a WebDriver server running (e.g., chromedriver). +//! To start chromedriver: `chromedriver --port=4444` + +extern crate spider; + +use spider::features::webdriver::{ + attempt_navigation, get_page_title, setup_driver_events, take_screenshot, +}; +use spider::features::webdriver_common::{WebDriverBrowser, WebDriverConfig}; +use spider::tokio; +use spider::website::Website; +use std::io::Result; +use std::time::Duration; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; + +async fn screenshot_website(url: &str, output_filename: &str) -> Result<()> { + let webdriver_config = WebDriverConfig::new() + .with_server_url("http://localhost:4444") + .with_browser(WebDriverBrowser::Chrome) + .with_headless(true) + .with_viewport(1920, 1080) + .with_timeout(Duration::from_secs(30)); + + let website: Website = Website::new(url) + .with_webdriver(webdriver_config.clone()) + .build() + .unwrap(); + + // Setup webdriver + if let Some(controller) = website.setup_webdriver().await { + let driver = controller.driver(); + + // Setup stealth mode + setup_driver_events(driver, &website.configuration).await; + + // Navigate to the URL + let timeout = Some(Duration::from_secs(30)); + if let Err(e) = attempt_navigation(url, driver, &timeout).await { + eprintln!("Failed to navigate to {}: {:?}", url, e); + return Ok(()); + } + + // Wait a bit for page to fully render + tokio::time::sleep(Duration::from_secs(2)).await; + + // Get page title + match get_page_title(driver).await { + Ok(title) => println!("Page title: {}", title), + Err(e) => eprintln!("Failed to get title: {:?}", e), + } + + // Take screenshot + match take_screenshot(driver).await { + Ok(png_data) => { + let mut file = File::create(output_filename).await?; + file.write_all(&png_data).await?; + println!("Screenshot saved to: {}", output_filename); + } + Err(e) => eprintln!("Failed to take screenshot: {:?}", e), + } + } else { + eprintln!("Failed to initialize WebDriver"); + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + screenshot_website("https://example.com", "example_screenshot.png").await?; + screenshot_website( + "https://choosealicense.com", + "choosealicense_screenshot.png", + ) + .await?; + + Ok(()) +} diff --git a/examples/whitelist.rs b/examples/whitelist.rs new file mode 100644 index 000000000..6140b3df4 --- /dev/null +++ b/examples/whitelist.rs @@ -0,0 +1,42 @@ +//! `cargo run --example whitelist --features regex` +extern crate spider; + +use spider::{tokio, website::Website}; +use tokio::io::AsyncWriteExt; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://rsseau.fr/en/"); + + website.with_whitelist_url(Some(vec!["/books".into()])); + + let mut rx2: tokio::sync::broadcast::Receiver = + website.subscribe(0).unwrap(); + let mut stdout = tokio::io::stdout(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + let _ = stdout + .write_all(format!("- {}\n", res.get_url()).as_bytes()) + .await; + } + stdout + }); + + let start = std::time::Instant::now(); + website.crawl().await; + website.unsubscribe(); + let duration = start.elapsed(); + let mut stdout = join_handle.await.unwrap(); + + let _ = stdout + .write_all( + format!( + "Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + duration, + website.get_links().len() + ) + .as_bytes(), + ) + .await; +} diff --git a/features.bzl b/features.bzl new file mode 100644 index 000000000..f55bd6c7b --- /dev/null +++ b/features.bzl @@ -0,0 +1,326 @@ +""" +Feature flag definitions for Spider project. +This file documents all feature flags, their default values, and their dependencies. +""" + +# Feature flag definitions with metadata +FEATURE_FLAGS = { + # Core features + "sync": { + "default": True, + "description": "Enable synchronous crawling API", + "rustc_flag": 'feature="sync"', + "deps": ["tokio/sync"], + }, + "serde": { + "default": True, + "description": "Enable serialization support", + "rustc_flag": 'feature="serde"', + "deps": ["serde", "serde_json", "serde_regex", "hashbrown/serde", "string-interner/serde", "smallvec/serde"], + }, + "encoding": { + "default": False, + "description": "Enable character encoding detection", + "rustc_flag": 'feature="encoding"', + "deps": [], + }, + "time": { + "default": False, + "description": "Enable time tracking", + "rustc_flag": 'feature="time"', + "deps": [], + }, + "cookies": { + "default": False, + "description": "Enable cookie support", + "rustc_flag": 'feature="cookies"', + "deps": ["cookie", "reqwest/cookies"], + }, + "headers": { + "default": False, + "description": "Enable header processing", + "rustc_flag": 'feature="headers"', + "deps": ["httpdate"], + }, + "glob": { + "default": False, + "description": "Enable URL glob pattern support", + "rustc_flag": 'feature="glob"', + "deps": ["itertools"], + }, + "full_resources": { + "default": False, + "description": "Download all page resources", + "rustc_flag": 'feature="full_resources"', + "deps": [], + }, + "socks": { + "default": False, + "description": "Enable SOCKS proxy support", + "rustc_flag": 'feature="socks"', + "deps": ["reqwest/socks"], + }, + + # Browser automation features + "chrome": { + "default": False, + "description": "Enable Chrome browser automation", + "rustc_flag": 'feature="chrome"', + "deps": ["chromey", "base64", "rand", "fastrand", "which", "home"], + "implies": ["serde", "cookies"], + }, + "chrome_headed": { + "default": False, + "description": "Run Chrome in headed mode", + "rustc_flag": 'feature="chrome_headed"', + "deps": [], + "implies": ["chrome"], + }, + "chrome_stealth": { + "default": False, + "description": "Enable Chrome stealth mode", + "rustc_flag": 'feature="chrome_stealth"', + "deps": [], + "implies": ["chrome"], + }, + "chrome_screenshot": { + "default": False, + "description": "Enable Chrome screenshot capability", + "rustc_flag": 'feature="chrome_screenshot"', + "deps": [], + "implies": ["chrome"], + }, + "chrome_intercept": { + "default": False, + "description": "Enable Chrome request interception", + "rustc_flag": 'feature="chrome_intercept"', + "deps": [], + "implies": ["chrome"], + }, + "smart": { + "default": False, + "description": "Enable smart crawling with Chrome interception", + "rustc_flag": 'feature="smart"', + "deps": [], + "implies": ["chrome", "chrome_intercept"], + }, + "webdriver": { + "default": False, + "description": "Enable WebDriver support", + "rustc_flag": 'feature="webdriver"', + "deps": ["thirtyfour", "rand", "fastrand"], + "implies": ["serde"], + }, + + # LLM integration features + "openai": { + "default": False, + "description": "Enable OpenAI integration", + "rustc_flag": 'feature="openai"', + "deps": ["async-openai", "tiktoken-rs", "serde_json"], + "implies": ["chrome", "chrome_intercept", "serde"], + }, + "gemini": { + "default": False, + "description": "Enable Gemini integration", + "rustc_flag": 'feature="gemini"', + "deps": ["gemini-rust", "serde_json"], + "implies": ["chrome", "chrome_intercept", "serde"], + }, + + # Linux-specific features + "io_uring": { + "default": True, + "description": "Enable io_uring for async I/O (Linux only)", + "rustc_flag": 'feature="io_uring"', + "deps": ["io-uring"], + "platform": "linux", + }, + + # Cache features + "cache": { + "default": False, + "description": "Enable HTTP request caching", + "rustc_flag": 'feature="cache"', + "deps": ["http-global-cache", "reqwest-middleware", "http-cache-reqwest", "http", "http-cache"], + "implies": ["cache_request"], + }, + "cache_mem": { + "default": False, + "description": "Enable in-memory HTTP caching", + "rustc_flag": 'feature="cache_mem"', + "deps": ["http-global-cache", "reqwest-middleware", "http-cache-reqwest", "http", "http-cache"], + "implies": ["cache_request"], + }, + "cache_openai": { + "default": False, + "description": "Enable OpenAI response caching", + "rustc_flag": 'feature="cache_openai"', + "deps": ["moka"], + }, + + # Scheduling features + "cron": { + "default": False, + "description": "Enable cron-based scheduling", + "rustc_flag": 'feature="cron"', + "deps": ["async_job", "chrono", "cron", "async-trait"], + }, + + # Other features + "sitemap": { + "default": False, + "description": "Enable sitemap parsing", + "rustc_flag": 'feature="sitemap"', + "deps": ["sitemap"], + }, + "tracing": { + "default": False, + "description": "Enable tracing support", + "rustc_flag": 'feature="tracing"', + "deps": ["tracing"], + "implies": ["tokio/tracing"], + }, + "firewall": { + "default": False, + "description": "Enable firewall detection", + "rustc_flag": 'feature="firewall"', + "deps": ["spider_firewall"], + "implies": ["chromey/firewall"], + }, + "wreq": { + "default": False, + "description": "Use wreq HTTP client instead of reqwest", + "rustc_flag": 'feature="wreq"', + "deps": ["wreq", "wreq-util"], + }, + "spider_cloud": { + "default": False, + "description": "Enable Spider Cloud integration", + "rustc_flag": 'feature="spider_cloud"', + "deps": [], + "implies": ["serde"], + }, + + # Agent features + "agent": { + "default": False, + "description": "Enable spider_agent integration", + "rustc_flag": 'feature="agent"', + "deps": ["spider_agent"], + }, + "agent_openai": { + "default": False, + "description": "Enable agent with OpenAI", + "rustc_flag": 'feature="agent_openai"', + "deps": [], + "implies": ["agent", "spider_agent/openai"], + }, + "agent_chrome": { + "default": False, + "description": "Enable agent with Chrome", + "rustc_flag": 'feature="agent_chrome"', + "deps": ["dashmap"], + "implies": ["agent", "chrome", "spider_agent/chrome"], + }, + "agent_webdriver": { + "default": False, + "description": "Enable agent with WebDriver", + "rustc_flag": 'feature="agent_webdriver"', + "deps": [], + "implies": ["agent", "spider_agent/webdriver"], + }, + "agent_skills": { + "default": False, + "description": "Enable agent skills", + "rustc_flag": 'feature="agent_skills"', + "deps": [], + "implies": ["agent", "spider_agent/skills"], + }, + "agent_full": { + "default": False, + "description": "Enable full agent feature set", + "rustc_flag": 'feature="agent_full"', + "deps": [], + "implies": ["agent", "agent_skills", "spider_agent/full"], + }, + + # Search providers + "search": { + "default": False, + "description": "Enable search functionality", + "rustc_flag": 'feature="search"', + "deps": [], + "implies": ["serde"], + }, + "search_serper": { + "default": False, + "description": "Enable Serper search provider", + "rustc_flag": 'feature="search_serper"', + "deps": [], + "implies": ["search"], + }, + "search_brave": { + "default": False, + "description": "Enable Brave search provider", + "rustc_flag": 'feature="search_brave"', + "deps": [], + "implies": ["search"], + }, + "search_bing": { + "default": False, + "description": "Enable Bing search provider", + "rustc_flag": 'feature="search_bing"', + "deps": [], + "implies": ["search"], + }, + "search_tavily": { + "default": False, + "description": "Enable Tavily search provider", + "rustc_flag": 'feature="search_tavily"', + "deps": [], + "implies": ["search"], + }, +} + +# Build configuration presets +BUILD_PRESETS = { + "basic": { + "description": "Basic crawling with minimal features", + "features": ["sync", "serde", "encoding", "time", "io_uring"], + }, + "full": { + "description": "All features enabled", + "features": list(FEATURE_FLAGS.keys()), + }, + "chrome": { + "description": "Chrome-based crawling", + "features": ["sync", "serde", "chrome", "encoding", "time"], + }, + "agent": { + "description": "Agent-based automation", + "features": ["sync", "serde", "agent_openai", "agent_chrome", "encoding", "time"], + }, +} + +def get_feature_deps(features): + """Get all dependencies for a set of features.""" + deps = [] + for feature in features: + if feature in FEATURE_FLAGS: + deps.extend(FEATURE_FLAGS[feature].get("deps", [])) + # Include implied features + for implied in FEATURE_FLAGS[feature].get("implies", []): + if isinstance(implied, str) and implied in FEATURE_FLAGS: + deps.extend(FEATURE_FLAGS[implied].get("deps", [])) + return deps + +def get_rustc_flags(features): + """Get all rustc flags for a set of features.""" + flags = [] + for feature in features: + if feature in FEATURE_FLAGS: + flag = FEATURE_FLAGS[feature].get("rustc_flag") + if flag: + flags.extend(["--cfg", flag]) + return flags \ No newline at end of file diff --git a/patch_module.sh b/patch_module.sh new file mode 100644 index 000000000..b234b09c1 --- /dev/null +++ b/patch_module.sh @@ -0,0 +1,2 @@ +sed -i '/crate.from_cargo(/i \ +crate.annotation(\n crate = "aws-lc-sys",\n build_script_env = {\n "CRATE_CC_NO_DEFAULTS": "1",\n "CFLAGS": "-O0",\n "OPT_LEVEL": "0",\n },\n)\n' MODULE.bazel diff --git a/spider/BUILD.bazel b/spider/BUILD.bazel new file mode 100644 index 000000000..76da75d12 --- /dev/null +++ b/spider/BUILD.bazel @@ -0,0 +1,323 @@ +""" +Spider - A web crawler and scraper, building blocks for data curation workloads. +Core library with extensive feature flag support. +""" + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "spider", + srcs = glob(["src/**/*.rs"]), + edition = "2021", + deps = [ + "//spider_agent_types:spider_agent_types", + # Core dependencies + "@crates_io//:url", + "@crates_io//:tokio-stream", + "@crates_io//:hashbrown", + "@crates_io//:log", + "@crates_io//:percent-encoding", + "@crates_io//:regex", + "@crates_io//:string_concat", + "@crates_io//:lazy_static", + "@crates_io//:ahash", + "@crates_io//:smallvec", + "@crates_io//:num_cpus", + "@crates_io//:bytes", + "@crates_io//:case_insensitive_string", + "@crates_io//:strum", + "@crates_io//:lol_html", + "@crates_io//:quick-xml", + "@crates_io//:phf", + "@crates_io//:auto_encoder", + "@crates_io//:aho-corasick", + "@crates_io//:h2", + "@crates_io//:tower", + "@crates_io//:pin-project-lite", + "@crates_io//:spider_fingerprint", + "@crates_io//:tokio", + "@crates_io//:reqwest", + "@crates_io//:libc", + "@crates_io//:flexbuffers", + ] + select({ + "//:serde_enabled": [ + "@crates_io//:serde", + "@crates_io//:serde_json", + "@crates_io//:serde_regex", + ], + "//conditions:default": [], + }) + select({ + "//:chrome_enabled": [ + "@crates_io//:chromey", + "@crates_io//:base64", + "@crates_io//:rand", + "@crates_io//:fastrand", + "@crates_io//:which", + "@crates_io//:home", + ], + "//conditions:default": [], + }) + select({ + "//:openai_enabled": [ + "@crates_io//:async-openai", + "@crates_io//:tiktoken-rs", + ], + "//conditions:default": [], + }) + select({ + "//:gemini_enabled": [ + "@crates_io//:gemini-rust", + ], + "//conditions:default": [], + }) + select({ + "//:io_uring_enabled": [ + "@crates_io//:io-uring", + ], + "//conditions:default": [], + }) + select({ + "//:webdriver_enabled": [ + "@crates_io//:thirtyfour", + ], + "//conditions:default": [], + }) + select({ + "//:cache_enabled": [ + "@crates_io//:http-global-cache", + "@crates_io//:reqwest-middleware", + "@crates_io//:http-cache-reqwest", + "@crates_io//:http", + "@crates_io//:http-cache", + ], + "//conditions:default": [], + }) + select({ + "//:cache_mem_enabled": [ + "@crates_io//:http-global-cache", + "@crates_io//:reqwest-middleware", + "@crates_io//:http-cache-reqwest", + "@crates_io//:http", + "@crates_io//:http-cache", + ], + "//conditions:default": [], + }) + select({ + "//:sitemap_enabled": [ + "@crates_io//:sitemap", + ], + "//conditions:default": [], + }) + select({ + "//:cron_enabled": [ + "@crates_io//:async_job", + "@crates_io//:chrono", + "@crates_io//:cron", + "@crates_io//:async-trait", + ], + "//conditions:default": [], + }) + select({ + "//:tracing_enabled": [ + "@crates_io//:tracing", + ], + "//conditions:default": [], + }) + select({ + "//:firewall_enabled": [ + "@crates_io//:spider_firewall", + ], + "//conditions:default": [], + }) + select({ + "//:wreq_enabled": [ + "@crates_io//:wreq", + "@crates_io//:wreq-util", + ], + "//conditions:default": [], + }) + select({ + "//:glob_enabled": [ + "@crates_io//:itertools", + ], + "//conditions:default": [], + }) + select({ + "//:headers_enabled": [ + "@crates_io//:httpdate", + ], + "//conditions:default": [], + }) + select({ + "//:cookies_enabled": [ + "@crates_io//:cookie", + ], + "//conditions:default": [], + }) + select({ + "//:agent_enabled": [ + "//spider_agent:spider_agent", + ], + "//conditions:default": [], + }) + select({ + "//:agent_chrome_enabled": [ + "//spider_agent:spider_agent", + "@crates_io//:dashmap", + ], + "//conditions:default": [], + }), + rustc_flags = [ + # Core feature flags + "--cfg", + 'feature="sync"', + "--cfg", + 'feature="encoding"', + "--cfg", + 'feature="time"', + "--cfg", + 'feature="flexbuffers"', + ] + select({ + "//:serde_enabled": [ + "--cfg", + 'feature="serde"', + ], + "//conditions:default": [], + }) + select({ + "//:chrome_enabled": [ + "--cfg", + 'feature="chrome"', + ], + "//conditions:default": [], + }) + select({ + "//:openai_enabled": [ + "--cfg", + 'feature="openai"', + ], + "//conditions:default": [], + }) + select({ + "//:gemini_enabled": [ + "--cfg", + 'feature="gemini"', + ], + "//conditions:default": [], + }) + select({ + "//:io_uring_enabled": [ + "--cfg", + 'feature="io_uring"', + ], + "//conditions:default": [], + }) + select({ + "//:webdriver_enabled": [ + "--cfg", + 'feature="webdriver"', + ], + "//conditions:default": [], + }) + select({ + "//:cache_enabled": [ + "--cfg", + 'feature="cache"', + ], + "//conditions:default": [], + }) + select({ + "//:cache_mem_enabled": [ + "--cfg", + 'feature="cache_mem"', + ], + "//conditions:default": [], + }) + select({ + "//:sitemap_enabled": [ + "--cfg", + 'feature="sitemap"', + ], + "//conditions:default": [], + }) + select({ + "//:smart_enabled": [ + "--cfg", + 'feature="smart"', + ], + "//conditions:default": [], + }) + select({ + "//:cron_enabled": [ + "--cfg", + 'feature="cron"', + ], + "//conditions:default": [], + }) + select({ + "//:tracing_enabled": [ + "--cfg", + 'feature="tracing"', + ], + "//conditions:default": [], + }) + select({ + "//:firewall_enabled": [ + "--cfg", + 'feature="firewall"', + ], + "//conditions:default": [], + }) + select({ + "//:wreq_enabled": [ + "--cfg", + 'feature="wreq"', + ], + "//conditions:default": [], + }) + select({ + "//:spider_cloud_enabled": [ + "--cfg", + 'feature="spider_cloud"', + ], + "//conditions:default": [], + }) + select({ + "//:glob_enabled": [ + "--cfg", + 'feature="glob"', + ], + "//conditions:default": [], + }) + select({ + "//:headers_enabled": [ + "--cfg", + 'feature="headers"', + ], + "//conditions:default": [], + }) + select({ + "//:cookies_enabled": [ + "--cfg", + 'feature="cookies"', + ], + "//conditions:default": [], + }) + select({ + "//:full_resources_enabled": [ + "--cfg", + 'feature="full_resources"', + ], + "//conditions:default": [], + }) + select({ + "//:socks_enabled": [ + "--cfg", + 'feature="socks"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_enabled": [ + "--cfg", + 'feature="agent"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_openai_enabled": [ + "--cfg", + 'feature="agent_openai"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_chrome_enabled": [ + "--cfg", + 'feature="agent_chrome"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_webdriver_enabled": [ + "--cfg", + 'feature="agent_webdriver"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_skills_enabled": [ + "--cfg", + 'feature="agent_skills"', + ], + "//conditions:default": [], + }) + select({ + "//:agent_full_enabled": [ + "--cfg", + 'feature="agent_full"', + ], + "//conditions:default": [], + }), +) \ No newline at end of file diff --git a/spider/Cargo.toml b/spider/Cargo.toml new file mode 100644 index 000000000..83dc38ebc --- /dev/null +++ b/spider/Cargo.toml @@ -0,0 +1,344 @@ +[package] +name = "spider" +version = "2.47.22" +authors = ["j-mendez "] +description = "A web crawler and scraper, building blocks for data curation workloads." +repository = "https://github.com/spider-rs/spider" +readme = "README.md" +keywords = ["crawler", "spider", "scraper"] +categories = ["web-programming", "command-line-utilities"] +license = "MIT" +documentation = "https://docs.rs/spider" +edition = "2021" + +[badges] +maintenance = { status = "as-is" } + +[dependencies] +url = "2" +tokio-stream = "0.1" +hashbrown = { version = "0.16", default-features = true } +log = "0.4" +percent-encoding = "2" +regex = { version = "1" } +ua_generator = { version = "^0.5", optional = true } +string_concat = "0.0.1" +lazy_static = "1" +ahash = { version = "0.8", default-features = false, features = ["std"] } +dashmap = { version = "6", optional = true } +smallvec = "1" +num_cpus = "1" +bytes = { version = "1", features = ["serde"] } +serde = { version = "1", optional = true, features = ["derive", "rc"] } +flexbuffers = { version = "25", optional = true } +itertools = { version = "0.14", optional = true } +case_insensitive_string = { version = "0.2", features = ["compact", "serde"] } +sitemap = { version = "0.4", optional = true } +chrono = { version = "0.4", optional = true } +cron = { version = "0.15", optional = true } +async-trait = { version = "0.1", optional = true } +strum = { version = "0.27", features = ["derive"] } +async_job = { version = "0.1", optional = true } +reqwest-middleware = { version = "0.5", optional = true, default-features = false } +http-cache-reqwest = { version = "1.0.0-alpha.5", optional = true, default-features = false } +const_format = { version = "0.2", optional = true } +async-openai = { version = "0.33", optional = true, features = ["chat-completion"] } +tiktoken-rs = { version = "0.9", optional = true } +gemini-rust = { version = "1.6", optional = true } +lol_html = { version = "2" } +cookie = { version = "0", optional = true } +serde_json = { version = "1", optional = true } +quick-xml = { version = "0.39", features = [ + "serde", + "serialize", + "async-tokio", +] } +moka = { version = "0.12", features = ["future"], optional = true } +fastrand = { version = "2", optional = true } +http-cache-semantics = { version = "3", optional = true } +http-cache = { version = "1.0.0-alpha.5", optional = true, default-features = false, features = ["url-standard"] } +http-global-cache = { version = "0.2.0", optional = true, default-features = false, features = ["cache_request"] } +http = { version = "1", optional = true } +phf = "0.11" +auto_encoder = { version = "0.1" } +base64 = { version = "0.22", optional = true } +string-interner = { version = "0.19", default-features = false, features = [ + "std", + "inline-more", + "backends", +], optional = true } +httpdate = { version = "1", optional = true } +rand = { version = "0.9", optional = true } +serde_regex = { version = "1", optional = true } +llm_json = { version = "1", optional = true } +thirtyfour = { version = "0.36", optional = true } +statrs = { version = "0.18", optional = true } +aho-corasick = { version = "1" } +tracing = { version = "0.1", default-features = false, features = [ + "std", + "attributes", +], optional = true } +sysinfo = { version = "0.38", default-features = false, features = [ + "system", +], optional = true } +sqlx = { version = "0.8", features = [ + "runtime-tokio", + "sqlite", +], optional = true } +h2 = "0.4" +tower = { version = "0.5", features = ["limit"] } +pin-project-lite = "0.2" +sonic-rs = { version = "0.5", optional = true } +wreq = { version = "5", optional = true, features = [ + "json", + "stream", + "socks", + "gzip", + "brotli", + "zstd", + "deflate", + "cookies", +] } +wreq-util = { version = "2", optional = true, features = ["emulation-serde"] } +which = { version = "6.0", optional = true } +home = { version = "0.5", optional = true } +libc = "0.2" + +[dependencies.chromey] +version = "2.40" +optional = true +default-features = false +features = ["bytes", "stream"] + +[dependencies.spider_firewall] +version = "2" +optional = true + +[dependencies.spider_fingerprint] +version = "2" +default-features = false +features = ["serde", "headers", "dynamic-versions"] + +[dependencies.spider_agent_types] +path = "../spider_agent_types" +version = "2.47.22" + +[dependencies.spider_agent] +path = "../spider_agent" +version = "2.47.22" +optional = true +default-features = false + +[target.'cfg(target_os = "linux")'.dependencies] +io-uring = { version = "0.7", optional = true } +libc = "0.2" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", default-features = false, features = [ + "macros", + "time", + "rt-multi-thread", +] } +fastrand = { version = "2", optional = true } +reqwest = { version = "0.13", features = [ + "brotli", + "gzip", + "deflate", + "zstd", + "stream", + "http2", + "query" +] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1", default-features = false, features = [ + "macros", + "time", + "rt", +] } +fastrand = { version = "2", optional = true, features = ["js"] } +reqwest = { version = "0.13", features = [ + "brotli", + "gzip", + "deflate", + "stream", + "http2", + "query" +] } + +[dev-dependencies] +serde_json = "1" +env_logger = "0.11" + +[features] +default = ["basic", "io_uring"] +__basic = [ + "sync", + "cookies", + "ua_generator", + "encoding", + "string_interner_buffer_backend", + "balance", + "real_browser", + "disk_native_tls", + "time" +] +basic_tls = [ + "reqwest_native_tls_native_roots", + "disk_native_tls", +] +basic = [ + "__basic", + "basic_tls" +] +disk = ["dep:sqlx"] +disk_native_tls = ["disk", "sqlx/runtime-tokio-native-tls"] +disk_aws = ["disk", "sqlx/tls-rustls-aws-lc-rs"] +adblock = ["chrome", "chromey/adblock"] +balance = ["dep:sysinfo"] +regex = [] +glob = ["dep:itertools"] +ua_generator = ["dep:ua_generator"] +decentralized = ["serde", "flexbuffers"] +control = [] +time = [] +cmd = ["tokio/process"] +io_uring = ["dep:io-uring"] +sync = ["tokio/sync"] +flexbuffers = ["dep:flexbuffers"] +serde = [ + "dep:serde", + "dep:serde_json", + "hashbrown/serde", + "string-interner/serde", + "dep:serde_regex", + "smallvec/serde", + "reqwest_json", +] +fs = ["tokio/fs"] +full_resources = [] +socks = ["reqwest/socks"] +reqwest_json = ["reqwest/json"] +sitemap = ["dep:sitemap"] +cache_request = ["dep:http-global-cache","dep:reqwest-middleware", "dep:http-cache-reqwest", "dep:http", "dep:http-cache"] +cache = ["cache_request", "http-global-cache/cache", "http-cache-reqwest/manager-cacache"] +cache_mem = ["cache_request", "http-global-cache/cache_mem"] +cache_openai = ["dep:moka"] +cache_chrome_hybrid = [ + "cache", + "chrome", + "dep:http-cache-semantics", + "dep:http-cache", + "dep:http", +] +cache_chrome_hybrid_mem = [ + "cache_mem", + "chrome", + "dep:http-cache-semantics", + "dep:http-cache", + "dep:http", +] +chrome = ["dep:chromey", "dep:base64", "dep:rand", "serde", "dep:fastrand", "dep:which", "dep:home", "cookies", "reqwest_json"] +chrome_headed = ["chrome"] +chrome_cpu = ["chrome"] +chrome_stealth = ["chrome"] +chrome_screenshot = ["chrome"] +chrome_store_page = ["chrome", "serde"] +chrome_intercept = ["chrome"] +chrome_headless_new = ["chrome"] +chrome_simd = ["chrome", "chromey/simd", "simd"] +chrome_tls_connection = ["chrome", "chromey/chrome_tls_connection"] +chrome_serde_stacker = ["chrome", "chromey/serde_stacker"] +chrome_remote_cache = ["chrome", "cache_mem", "chromey/_cache", "chromey/cache_mem"] +chrome_remote_cache_disk = ["chrome_remote_cache", "chromey/cache"] +chrome_remote_cache_mem = ["chrome_remote_cache"] +cookies = ["reqwest/cookies", "dep:cookie"] +cron = ["dep:async_job", "dep:chrono", "dep:cron", "dep:async-trait"] +smart = ["chrome", "chrome_intercept"] +encoding = [] +headers = ["dep:httpdate"] +remote_addr = [] +real_browser = ["dep:statrs", "dep:rand", "dep:fastrand"] +openai = [ + "chrome", + "serde", + "chrome_intercept", + "dep:async-openai", + "dep:tiktoken-rs", + "dep:serde_json", +] +openai_slim_fit = [] +gemini = [ + "chrome", + "serde", + "chrome_intercept", + "dep:gemini-rust", + "dep:serde_json", +] +cache_gemini = ["dep:moka"] +decentralized_headers = ["dep:const_format", "dep:itertools"] +spoof = ["dep:fastrand"] +reqwest_rustls_tls = ["reqwest/rustls"] +reqwest_native_tls = ["reqwest/native-tls"] +reqwest_native_tls_alpn = ["reqwest/native-tls"] +reqwest_native_tls_vendored = ["reqwest/native-tls-vendored"] +reqwest_native_tls_manual_roots = ["reqwest/rustls"] +reqwest_native_tls_webpki_roots = ["reqwest/rustls"] +reqwest_native_tls_native_roots = ["reqwest/rustls"] +reqwest_hickory_dns = ["reqwest/hickory-dns"] +reqwest_multipart = ["reqwest/multipart"] +tokio_io_std = ["tokio/io-std"] +tracing = ["tokio/tracing", "dep:tracing"] +string_interner_buffer_backend = ["dep:string-interner"] +string_interner_string_backend = ["dep:string-interner"] +string_interner_bucket_backend = ["dep:string-interner"] +page_error_status_details = [] +simd = ["dep:sonic-rs"] +firewall = ["dep:spider_firewall", "chromey/firewall"] +wreq = ["dep:wreq", "dep:wreq-util"] +rquest_hickory_dns = ["wreq/hickory-dns"] +extra_information = [] +llm_json = ["dep:llm_json"] +webdriver = ["dep:thirtyfour", "serde", "dep:rand", "dep:fastrand"] +webdriver_headed = ["webdriver"] +webdriver_stealth = ["webdriver"] +webdriver_chrome = ["webdriver"] +webdriver_firefox = ["webdriver"] +webdriver_edge = ["webdriver"] +webdriver_screenshot = ["webdriver"] +search = ["serde"] +search_serper = ["search"] +search_brave = ["search"] +search_bing = ["search"] +search_tavily = ["search"] + +# Spider Cloud integration - anti-bot, proxy, API modes +spider_cloud = ["serde", "reqwest_json"] + +# Agent features - enables spider_agent with sub-features +agent = ["dep:spider_agent"] +agent_openai = ["agent", "spider_agent/openai"] +agent_chrome = ["agent", "spider_agent/chrome", "chrome", "dep:dashmap"] +agent_webdriver = ["agent", "spider_agent/webdriver"] +agent_skills = ["agent", "spider_agent/skills"] +agent_skills_s3 = ["agent_skills", "spider_agent/skills_s3"] +agent_fs = ["agent", "spider_agent/fs"] +agent_search_serper = ["agent", "spider_agent/search_serper"] +agent_search_brave = ["agent", "spider_agent/search_brave"] +agent_search_bing = ["agent", "spider_agent/search_bing"] +agent_search_tavily = ["agent", "spider_agent/search_tavily"] +agent_full = ["agent", "agent_skills", "spider_agent/full"] + +# Work-stealing / hedged requests for slow crawls. +hedge = [] + +# Allows full concurrency. +cowboy = [] + +# Enables usage of `#[inline]` on far more functions than by default in this +# crate. This may lead to a performance increase but often comes at a compile +# time cost. +inline-more = [] + +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/spider/LICENSE b/spider/LICENSE new file mode 100644 index 000000000..ea5b60640 --- /dev/null +++ b/spider/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/spider/README.md b/spider/README.md new file mode 100644 index 000000000..08b294be9 --- /dev/null +++ b/spider/README.md @@ -0,0 +1,587 @@ +# Spider + +![crate version](https://img.shields.io/crates/v/spider.svg) + +Multithreaded async crawler/indexer using [isolates](https://research.cs.wisc.edu/areas/os/Seminar/schedules/papers/Deconstructing_Process_Isolation_final.pdf) and IPC channels for communication with the ability to run decentralized. + +## Dependencies + +On Linux + +- OpenSSL 1.0.1, 1.0.2, 1.1.0, or 1.1.1 + +## Example + +This is a basic async example crawling a web page, add spider to your `Cargo.toml`: + +```toml +[dependencies] +spider = "2" +``` + +And then the code: + +```rust,no_run +extern crate spider; + +use spider::website::Website; +use spider::tokio; + +#[tokio::main] +async fn main() { + let url = "https://choosealicense.com"; + let mut website = Website::new(&url); + website.crawl().await; + + for link in website.get_links() { + println!("- {:?}", link.as_ref()); + } +} +``` + +You can use `Configuration` object to configure your crawler: + +```rust +// .. +let mut website = Website::new("https://choosealicense.com"); + +website.configuration.respect_robots_txt = true; +website.configuration.subdomains = true; +website.configuration.tld = false; +website.configuration.delay = 0; // Defaults to 0 ms due to concurrency handling +website.configuration.request_timeout = None; // Defaults to 15000 ms +website.configuration.http2_prior_knowledge = false; // Enable if you know the webserver supports http2 +website.configuration.user_agent = Some("myapp/version".into()); // Defaults to using a random agent +website.on_link_find_callback = Some(|s, html| { println!("link target: {}", s); (s, html)}); // Callback to run on each link find - useful for mutating the url, ex: convert the top level domain from `.fr` to `.es`. +website.configuration.blacklist_url.get_or_insert(Default::default()).push("https://choosealicense.com/licenses/".into()); +website.configuration.proxies.get_or_insert(Default::default()).push("socks5://10.1.1.1:12345".into()); // Defaults to None - proxy list. +website.configuration.budget = Some(spider::hashbrown::HashMap::from([(spider::CaseInsensitiveString::new("*"), 300), (spider::CaseInsensitiveString::new("/licenses"), 10)])); // Defaults to None. +website.configuration.cron_str = "1/5 * * * * *".into(); // Defaults to empty string - Requires the `cron` feature flag +website.configuration.cron_type = spider::website::CronType::Crawl; // Defaults to CronType::Crawl - Requires the `cron` feature flag +website.configuration.limit = 300; // The limit of pages crawled. By default there is no limit. +website.configuration.cache = false; // HTTP caching. Requires the `cache` or `chrome` feature flag. + +website.crawl().await; +``` + +The builder pattern is also available v1.33.0 and up: + +```rust +let mut website = Website::new("https://choosealicense.com"); + +website + .with_respect_robots_txt(true) + .with_subdomains(true) + .with_tld(false) + .with_delay(0) + .with_request_timeout(None) + .with_http2_prior_knowledge(false) + .with_user_agent(Some("myapp/version".into())) + .with_budget(Some(spider::hashbrown::HashMap::from([("*", 300), ("/licenses", 10)]))) + .with_limit(300) + .with_caching(false) + .with_external_domains(Some(Vec::from(["https://creativecommons.org/licenses/by/3.0/"].map( |d| d.to_string())).into_iter())) + .with_headers(None) + .with_blacklist_url(Some(Vec::from(["https://choosealicense.com/licenses/".into()]))) + .with_cron("1/5 * * * * *", Default::Default()) + .with_proxies(None); +``` + +## Features + +We have the following optional feature flags. + +```toml +[dependencies] +spider = { version = "2", features = ["regex", "ua_generator"] } +``` + +1. `ua_generator`: Enables auto generating a random real User-Agent. +1. `regex`: Enables blacklisting and whitelisting paths with regex. +1. `disk`: Enables SQLite hybrid disk storage to balance memory usage with no tls. +1. `disk_native_tls`: Enables SQLite hybrid disk storage to balance memory usage with native tls. +1. `disk_aws`: Enables SQLite hybrid disk storage to balance memory usage with aws_tls. +1. `balance`: Enables balancing the CPU and memory to scale more efficiently. +1. `decentralized`: Enables decentralized processing of IO, requires the [spider_worker](../spider_worker/README.md) startup before crawls. +1. `sync`: Subscribe to changes for Page data processing async. [Enabled by default] +1. `control`: Enables the ability to pause, start, and shutdown crawls on demand. +1. `full_resources`: Enables gathering all content that relates to the domain like CSS, JS, and etc. +1. `serde`: Enables serde serialization support. +1. `socks`: Enables socks5 proxy support. +1. `glob`: Enables [url glob](https://everything.curl.dev/cmdline/globbing) support. +1. `fs`: Enables storing resources to disk for parsing (may greatly increases performance at the cost of temp storage). +1. `sitemap`: Include sitemap pages in results. +1. `time`: Enables duration tracking per page. +1. `cache`: Enables HTTP caching request to disk. +1. `cache_mem`: Enables HTTP caching request to persist in memory. +1. `cache_chrome_hybrid`: Enables hybrid chrome request caching between HTTP. +1. `cache_openai`: Enables caching the OpenAI request. This can drastically save costs when developing AI workflows. +1. `chrome`: Enables chrome headless rendering, use the env var `CHROME_URL` to connect remotely. +1. `chrome_store_page`: Store the page object to perform other actions. The page may be closed. +1. `chrome_screenshot`: Enables storing a screenshot of each page on crawl. Defaults the screenshots to the ./storage/ directory. Use the env variable `SCREENSHOT_DIRECTORY` to adjust the directory. To save the background set the env var `SCREENSHOT_OMIT_BACKGROUND` to false. +1. `chrome_headed`: Enables chrome rendering headful rendering. +1. `chrome_headless_new`: Use headless=new to launch the browser. +1. `chrome_cpu`: Disable gpu usage for chrome browser. +1. `chrome_stealth`: Enables stealth mode to make it harder to be detected as a bot. +1. `chrome_intercept`: Allows intercepting network request to speed up processing. +1. `chrome_remote_cache`: Use a remote cache for chrome and HTTP (hybrid) - view the [chromey remote caching](https://github.com/spider-rs/chromey?tab=readme-ov-file#remote-caching) to learn more. +1. `adblock`: Enables the ability to block ads when using chrome and chrome_intercept. +1. `cookies`: Enables cookies storing and setting to use for request. +1. `real_browser`: Enables the ability to bypass protected pages. +1. `cron`: Enables the ability to start cron jobs for the website. +1. `spoof`: Spoof HTTP headers for the request. +1. `openai`: Enables OpenAI to generate dynamic browser executable scripts. Make sure to use the env var `OPENAI_API_KEY`. +1. `smart`: Enables smart mode. This runs request as HTTP until JavaScript rendering is needed. This avoids sending multiple network request by re-using the content. +1. `encoding`: Enables handling the content with different encodings like Shift_JIS. +1. `headers`: Enables the extraction of header information on each retrieved page. Adds a `headers` field to the page struct. +1. `decentralized_headers`: Enables the extraction of suppressed header information of the decentralized processing of IO. +This is needed if `headers` is set in both [spider](../spider/README.md) and [spider_worker](../spider_worker/README.md). +1. `string_interner_buffer_backend`: Enables the String interning using the buffer backend [default]. +1. `string_interner_string_backend`: Enables the String interning using the string backend. +1. `string_interner_bucket_backend`: Enables the String interning using the bucket backend. +1. `spider_cloud`: Use [spider.cloud](https://spider.cloud) for anti-bot bypass and proxy rotation. + +### Decentralization + +Move processing to a worker, drastically increases performance even if worker is on the same machine due to efficient runtime split IO work. + +```toml +[dependencies] +spider = { version = "2", features = ["decentralized"] } +``` + +```sh +# install the worker +cargo install spider_worker +# start the worker [set the worker on another machine in prod] +RUST_LOG=info SPIDER_WORKER_PORT=3030 spider_worker +# start rust project as normal with SPIDER_WORKER env variable +SPIDER_WORKER=http://127.0.0.1:3030 cargo run --example example --features decentralized +``` + +The `SPIDER_WORKER` env variable takes a comma seperated list of urls to set the workers. If the `scrape` feature flag is enabled, use the `SPIDER_WORKER_SCRAPER` env variable to determine the scraper worker. + +### Handling headers with decentralisation + +Without decentralisation the values of the headers for a page are unmodified. +When working with decentralized workers, each worker stores the headers retrieved +for the original request with prefixed element names (`"zz-spider-r--"`). + +Using the feature `decentralized_headers` provides some useful tools to clean and extract the original header +entries under `spider::features::decentralized_headers`. + +[WORKER_SUPPRESSED_HEADER_PREFIX] + +### Subscribe to changes + +Use the subscribe method to get a broadcast channel. + +```toml +[dependencies] +spider = { version = "2", features = ["sync"] } +``` + +```rust,no_run +extern crate spider; + +use spider::website::Website; +use spider::tokio; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://choosealicense.com"); + let mut rx2 = website.subscribe(16).unwrap(); + + tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + } + }); + + website.crawl().await; + website.unsubscribe(); +} +``` + +### Regex Blacklisting + +Allow regex for blacklisting routes + +```toml +[dependencies] +spider = { version = "2", features = ["regex"] } +``` + +```rust,no_run +extern crate spider; + +use spider::website::Website; +use spider::tokio; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://choosealicense.com"); + website.configuration.blacklist_url.push("/licenses/".into()); + website.crawl().await; + + for link in website.get_links() { + println!("- {:?}", link.as_ref()); + } +} +``` + +### Pause, Resume, and Shutdown + +If you are performing large workloads you may need to control the crawler by enabling the `control` feature flag: + +```toml +[dependencies] +spider = { version = "2", features = ["control"] } +``` + +```rust +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + use spider::utils::{pause, resume, shutdown}; + let url = "https://choosealicense.com/"; + let mut website: Website = Website::new(&url); + + tokio::spawn(async move { + pause(url).await; + sleep(tokio::time::Duration::from_millis(5000)).await; + resume(url).await; + // perform shutdown if crawl takes longer than 15s + sleep(tokio::time::Duration::from_millis(15000)).await; + // you could also abort the task to shutdown crawls if using website.crawl in another thread. + shutdown(url).await; + }); + + website.crawl().await; +} +``` + +### Scrape/Gather HTML + +```rust +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + use std::io::{Write, stdout}; + + let url = "https://choosealicense.com/"; + let mut website = Website::new(&url); + + website.scrape().await; + + let mut lock = stdout().lock(); + + let separator = "-".repeat(url.len()); + + for page in website.get_pages().unwrap().iter() { + writeln!( + lock, + "{}\n{}\n\n{}\n\n{}", + separator, + page.get_url_final(), + page.get_html(), + separator + ) + .unwrap(); + } +} +``` + +### Cron Jobs + +Use cron jobs to run crawls continuously at anytime. + +```toml +[dependencies] +spider = { version = "2", features = ["sync", "cron"] } +``` + +```rust,no_run +extern crate spider; + +use spider::website::{Website, run_cron}; +use spider::tokio; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://choosealicense.com"); + // set the cron to run or use the builder pattern `website.with_cron`. + website.cron_str = "1/5 * * * * *".into(); + + let mut rx2 = website.subscribe(16).unwrap(); + + let join_handle = tokio::spawn(async move { + while let Ok(res) = rx2.recv().await { + println!("{:?}", res.get_url()); + } + }); + + // take ownership of the website. You can also use website.run_cron, except you need to perform abort manually on handles created. + let mut runner = run_cron(website).await; + + println!("Starting the Runner for 10 seconds"); + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + let _ = tokio::join!(runner.stop(), join_handle); +} +``` + +### Chrome + +Connecting to Chrome can be done using the ENV variable `CHROME_URL`, if no connection is found a new browser is launched on the system. You do not need a chrome installation if you are connecting remotely. If you are not scraping content for downloading use +the feature flag [`chrome_intercept`] to possibly speed up request using Network Interception. + +```toml +[dependencies] +spider = { version = "2", features = ["chrome", "chrome_intercept"] } +``` + +You can use `website.crawl_concurrent_raw` to perform a crawl without chromium when needed. Use the feature flag `chrome_headed` to enable headful browser usage if needed to debug. + +```rust +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://spider.cloud") + .with_chrome_intercept(spider::features::chrome_common::RequestInterceptConfiguration::new(true)) + .build() + .unwrap(); + + website.crawl().await; + + println!("Links found {:?}", website.get_links().len()); +} +``` + +### Caching + +Enabling HTTP cache can be done with the feature flag [`cache`] or [`cache_mem`]. + +```toml +[dependencies] +spider = { version = "2.0.12", features = ["cache"] } +``` + +You need to set `website.cache` to true to enable as well. + +```rust +extern crate spider; + +use spider::tokio; +use spider::website::Website; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://spider.cloud") + .with_caching(true) + .build() + .unwrap(); + + website.crawl().await; + + println!("Links found {:?}", website.get_links().len()); + /// next run to website.crawl().await; will be faster since content is stored on disk. +} +``` + +### Smart Mode + +Intelligently run crawls using HTTP and JavaScript Rendering when needed. The best of both worlds to maintain speed and extract every page. This requires a chrome connection or browser installed on the system. + +```toml +[dependencies] +spider = { version = "2.0.12", features = ["smart"] } +``` + +```rust,no_run +extern crate spider; + +use spider::website::Website; +use spider::tokio; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://choosealicense.com"); + website.crawl_smart().await; + + for link in website.get_links() { + println!("- {:?}", link.as_ref()); + } +} +``` + +### OpenAI + +Use OpenAI to generate dynamic scripts to drive the browser done with the feature flag [`openai`]. + +```toml +[dependencies] +spider = { version = "2.0.12", features = ["openai"] } +``` + +```rust +extern crate spider; + +use spider::{tokio, website::Website, configuration::GPTConfigs}; + +#[tokio::main] +async fn main() { + let mut website: Website = Website::new("https://google.com") + .with_openai(Some(GPTConfigs::new("gpt-4-1106-preview", "Search for Movies", 256))) + .with_limit(1) + .build() + .unwrap(); + + website.crawl().await; +} +``` + +### Depth + +Set a depth limit to prevent forwarding. + +```toml +[dependencies] +spider = { version = "2.0.12" } +``` + +```rust,no_run +extern crate spider; + +use spider::{tokio, website::Website}; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://choosealicense.com").with_depth(3).build().unwrap(); + website.crawl().await; + + for link in website.get_links() { + println!("- {:?}", link.as_ref()); + } +} +``` + +### Reusable Configuration + +It is possible to re-use the same configuration for a crawl list. + +```rust +extern crate spider; + +use spider::configuration::Configuration; +use spider::{tokio, website::Website}; +use std::io::Error; +use std::time::Instant; + +const CAPACITY: usize = 5; +const CRAWL_LIST: [&str; CAPACITY] = [ + "https://spider.cloud", + "https://choosealicense.com", + "https://jeffmendez.com", + "https://spider-rs.github.io/spider-nodejs/", + "https://spider-rs.github.io/spider-py/", +]; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let config = Configuration::new() + .with_user_agent(Some("SpiderBot")) + .with_blacklist_url(Some(Vec::from(["https://spider.cloud/resume".into()]))) + .with_subdomains(false) + .with_tld(false) + .with_redirect_limit(3) + .with_retry(1) + .with_respect_robots_txt(true) + .with_external_domains(Some( + Vec::from(["http://loto.rsseau.fr/"].map(|d| d.to_string())).into_iter(), + )) + .build(); + + let mut handles = Vec::with_capacity(CAPACITY); + + for website_url in CRAWL_LIST { + match Website::new(website_url) + .with_config(config.to_owned()) + .build() + { + Ok(mut website) => { + let handle = tokio::spawn(async move { + println!("Starting Crawl - {:?}", website.get_url().inner()); + + let start = Instant::now(); + website.crawl().await; + let duration = start.elapsed(); + + let links = website.get_links(); + + for link in links { + println!("- {:?}", link.as_ref()); + } + + println!( + "{:?} - Time elapsed in website.crawl() is: {:?} for total pages: {:?}", + website.get_url().inner(), + duration, + links.len() + ); + }); + + handles.push(handle); + } + Err(e) => println!("{:?}", e) + } + } + + for handle in handles { + let _ = handle.await; + } + + Ok(()) +} + +``` + +### Blocking + +If you need a blocking sync implementation use a version prior to `v1.12.0`. + +### Spider Cloud + +Use [spider.cloud](https://spider.cloud) to help with hard-to-crawl pages. + +```toml +[dependencies] +spider = { version = "2", features = ["spider_cloud"] } +``` + +```rust,no_run +extern crate spider; + +use spider::website::Website; +use spider::tokio; + +#[tokio::main] +async fn main() { + let mut website = Website::new("https://example.com"); + website.with_spider_cloud("your-api-key"); + website.crawl().await; +} +``` + +Modes: **Proxy** (default), **Api**, **Unblocker**, **Fallback**, **Smart** (proxy + auto-fallback on bot protection). diff --git a/spider/src/client.rs b/spider/src/client.rs new file mode 100644 index 000000000..ac596cafd --- /dev/null +++ b/spider/src/client.rs @@ -0,0 +1,48 @@ +/// The asynchronous Client to make requests with. +#[cfg(all(not(feature = "cache_request"), not(feature = "wreq")))] +pub type Client = reqwest::Client; +#[cfg(all(not(feature = "cache_request"), not(feature = "wreq")))] +/// The asynchronous Client Builder. +pub type ClientBuilder = reqwest::ClientBuilder; +#[cfg(all(not(feature = "cache_request"), not(feature = "wreq")))] +pub use reqwest as request_client; +#[cfg(all( + feature = "cookies", + not(feature = "cache_request"), + not(feature = "wreq") +))] +pub use reqwest::cookie; +#[cfg(all(not(feature = "cache_request"), not(feature = "wreq")))] +pub use reqwest::{header, redirect, Error, Proxy, Response, StatusCode}; + +/// The asynchronous Client to make requests with wreq. +#[cfg(all(not(feature = "cache_request"), feature = "wreq"))] +pub type Client = wreq::Client; +#[cfg(all(not(feature = "cache_request"), feature = "wreq"))] +/// The asynchronous Client Builder. +pub type ClientBuilder = wreq::ClientBuilder; +#[cfg(all(not(feature = "cache_request"), feature = "wreq"))] +pub use wreq as request_client; +#[cfg(all(feature = "cookies", not(feature = "cache_request"), feature = "wreq"))] +pub use wreq::cookie; +#[cfg(all(not(feature = "cache_request"), feature = "wreq"))] +pub use wreq::{header, redirect, Error, Proxy, Response, StatusCode}; +#[cfg(all(not(feature = "cache_request"), feature = "wreq"))] +pub use wreq_util; + +/// The asynchronous Client to make requests with HTTP Cache. +#[cfg(feature = "cache_request")] +pub type Client = reqwest_middleware::ClientWithMiddleware; + +#[cfg(feature = "cache_request")] +/// The asynchronous Client Builder. +pub type ClientBuilder = reqwest::ClientBuilder; + +#[cfg(all(feature = "cookies", feature = "cache_request"))] +pub use reqwest::cookie; +#[cfg(feature = "cache_request")] +pub use reqwest::{header, redirect, Proxy, Response, StatusCode}; +#[cfg(feature = "cache_request")] +pub use reqwest_middleware as request_client; +#[cfg(feature = "cache_request")] +pub use reqwest_middleware::Error; diff --git a/spider/src/configuration.rs b/spider/src/configuration.rs new file mode 100644 index 000000000..296d28094 --- /dev/null +++ b/spider/src/configuration.rs @@ -0,0 +1,2216 @@ +use crate::compact_str::CompactString; +use crate::features::chrome_common::RequestInterceptConfiguration; +pub use crate::features::chrome_common::{ + AuthChallengeResponse, AuthChallengeResponseResponse, AutomationScripts, AutomationScriptsMap, + CaptureScreenshotFormat, CaptureScreenshotParams, ClipViewport, ExecutionScripts, + ExecutionScriptsMap, ScreenShotConfig, ScreenshotParams, Viewport, WaitFor, WaitForDelay, + WaitForIdleNetwork, WaitForSelector, WebAutomation, +}; +pub use crate::features::gemini_common::GeminiConfigs; +pub use crate::features::openai_common::GPTConfigs; +#[cfg(feature = "search")] +pub use crate::features::search::{ + SearchError, SearchOptions, SearchResult, SearchResults, TimeRange, +}; +pub use crate::features::webdriver_common::{WebDriverBrowser, WebDriverConfig}; +use crate::utils::get_domain_from_url; +use crate::utils::BasicCachePolicy; +use crate::website::CronType; +use reqwest::header::{AsHeaderName, HeaderMap, HeaderName, HeaderValue, IntoHeaderName}; +use std::net::IpAddr; +use std::sync::Arc; +use std::time::Duration; + +#[cfg(feature = "chrome")] +pub use spider_fingerprint::Fingerprint; + +/// Redirect policy configuration for request +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RedirectPolicy { + #[default] + #[cfg_attr( + feature = "serde", + serde(alias = "Loose", alias = "loose", alias = "LOOSE",) + )] + /// A loose policy that allows all request up to the redirect limit. + Loose, + #[cfg_attr( + feature = "serde", + serde(alias = "Strict", alias = "strict", alias = "STRICT",) + )] + /// A strict policy only allowing request that match the domain set for crawling. + Strict, + #[cfg_attr( + feature = "serde", + serde(alias = "None", alias = "none", alias = "NONE",) + )] + /// Prevent all redirects. + None, +} + +#[cfg(not(feature = "regex"))] +/// Allow list normal matching paths. +pub type AllowList = Vec; + +#[cfg(feature = "regex")] +/// Allow list regex. +pub type AllowList = Box; + +/// Whitelist or Blacklist +#[derive(Debug, Default, Clone)] +#[cfg_attr(not(feature = "regex"), derive(PartialEq, Eq))] +pub struct AllowListSet(pub AllowList); + +#[cfg(feature = "chrome")] +/// Track the events made via chrome. +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ChromeEventTracker { + /// Track the responses. + pub responses: bool, + /// Track the requests. + pub requests: bool, + /// Track the changes between web automation. + pub automation: bool, +} + +#[cfg(feature = "chrome")] +impl ChromeEventTracker { + /// Create a new chrome event tracker + pub fn new(requests: bool, responses: bool) -> Self { + ChromeEventTracker { + requests, + responses, + automation: true, + } + } +} + +#[cfg(feature = "sitemap")] +#[derive(Debug, Default)] +/// Determine if the sitemap modified to the whitelist. +pub struct SitemapWhitelistChanges { + /// Added the default sitemap.xml whitelist. + pub added_default: bool, + /// Added the custom whitelist path. + pub added_custom: bool, +} + +#[cfg(feature = "sitemap")] +impl SitemapWhitelistChanges { + /// Was the whitelist modified? + pub(crate) fn modified(&self) -> bool { + self.added_default || self.added_custom + } +} + +/// Determine allow proxy +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ProxyIgnore { + /// Chrome proxy. + Chrome, + /// HTTP proxy. + Http, + #[default] + /// Do not ignore + No, +} + +/// The networking proxy to use. +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RequestProxy { + /// The proxy address. + pub addr: String, + /// Ignore the proxy when running a request type. + pub ignore: ProxyIgnore, +} + +/// Structure to configure `Website` crawler +/// ```rust +/// use spider::website::Website; +/// let mut website: Website = Website::new("https://choosealicense.com"); +/// website.configuration.blacklist_url.insert(Default::default()).push("https://choosealicense.com/licenses/".to_string().into()); +/// website.configuration.respect_robots_txt = true; +/// website.configuration.subdomains = true; +/// website.configuration.tld = true; +/// ``` +#[derive(Debug, Default, Clone)] +#[cfg_attr( + all( + not(feature = "regex"), + not(feature = "openai"), + not(feature = "cache_openai"), + not(feature = "gemini"), + not(feature = "cache_gemini") + ), + derive(PartialEq) +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct Configuration { + /// Respect robots.txt file and not scrape not allowed files. This may slow down crawls if robots.txt file has a delay included. + pub respect_robots_txt: bool, + /// Allow sub-domains. + pub subdomains: bool, + /// Allow all tlds for domain. + pub tld: bool, + /// The max timeout for the crawl. + pub crawl_timeout: Option, + /// Preserve the HTTP host header from being included. + pub preserve_host_header: bool, + /// List of pages to not crawl. [optional: regex pattern matching] + pub blacklist_url: Option>, + /// List of pages to only crawl. [optional: regex pattern matching] + pub whitelist_url: Option>, + /// User-Agent for request. + pub user_agent: Option>, + /// Polite crawling delay in milli seconds. + pub delay: u64, + /// Request max timeout per page. By default the request times out in 15s. Set to None to disable. + pub request_timeout: Option, + /// Use HTTP2 for connection. Enable if you know the website has http2 support. + pub http2_prior_knowledge: bool, + /// Use proxy list for performing network request. + pub proxies: Option>, + /// Headers to include with request. + pub headers: Option>, + #[cfg(feature = "sitemap")] + /// Include a sitemap in response of the crawl. + pub sitemap_url: Option>, + #[cfg(feature = "sitemap")] + /// Prevent including the sitemap links with the crawl. + pub ignore_sitemap: bool, + /// The max redirections allowed for request. + pub redirect_limit: usize, + /// The redirect policy type to use. + pub redirect_policy: RedirectPolicy, + #[cfg(feature = "cookies")] + /// Cookie string to use for network requests ex: "foo=bar; Domain=blog.spider" + pub cookie_str: String, + #[cfg(feature = "wreq")] + /// The type of request emulation. This does nothing without the flag `sync` enabled. + pub emulation: Option, + #[cfg(feature = "cron")] + /// Cron string to perform crawls - use to help generate a valid cron for needs. + pub cron_str: String, + #[cfg(feature = "cron")] + /// The type of cron to run either crawl or scrape. + pub cron_type: CronType, + /// The max depth to crawl for a website. Defaults to 25 to help prevent infinite recursion. + pub depth: usize, + /// The depth to crawl pertaining to the root. + pub depth_distance: usize, + /// Use stealth mode for requests. + pub stealth_mode: spider_fingerprint::configs::Tier, + /// Configure the viewport for chrome and viewport headers. + pub viewport: Option, + /// Crawl budget for the paths. This helps prevent crawling extra pages and limiting the amount. + pub budget: Option>, + /// If wild card budgeting is found for the website. + pub wild_card_budgeting: bool, + /// External domains to include case-insensitive. + pub external_domains_caseless: + Arc>, + /// Collect all the resources found on the page. + pub full_resources: bool, + /// Dangerously accept invalid certficates. + pub accept_invalid_certs: bool, + /// The auth challenge response. The 'chrome_intercept' flag is also required in order to intercept the response. + pub auth_challenge_response: Option, + /// The OpenAI configs to use to help drive the chrome browser. This does nothing without the 'openai' flag. + pub openai_config: Option>, + /// The Gemini configs to use to help drive the chrome browser. This does nothing without the 'gemini' flag. + pub gemini_config: Option>, + /// Remote multimodal automation config (vision + LLM-driven steps). + /// Requires the `agent` feature for full functionality, uses stub type otherwise. + pub remote_multimodal: Option>, + /// Use a shared queue strategy when crawling. This can scale workloads evenly that do not need priority. + pub shared_queue: bool, + /// Return the page links in the subscription channels. This does nothing without the flag `sync` enabled. + pub return_page_links: bool, + /// Retry count to attempt to swap proxies etc. + pub retry: u8, + /// Skip spawning a control thread that can pause, start, and shutdown the crawl. + pub no_control_thread: bool, + /// The blacklist urls. + blacklist: AllowListSet, + /// The whitelist urls. + whitelist: AllowListSet, + /// Crawl budget for the paths. This helps prevent crawling extra pages and limiting the amount. + pub(crate) inner_budget: + Option>, + /// Expect only to handle HTML to save on resources. This mainly only blocks the crawling and returning of resources from the server. + pub only_html: bool, + /// The concurrency limits to apply. + pub concurrency_limit: Option, + /// Normalize the html de-deplucating the content. + pub normalize: bool, + /// Share the state of the crawl requires the 'disk' feature flag. + pub shared: bool, + /// Modify the headers to act like a real-browser + pub modify_headers: bool, + /// Modify the HTTP client headers only to act like a real-browser + pub modify_http_client_headers: bool, + /// Cache the page following HTTP caching rules. + #[cfg(any( + feature = "cache_request", + feature = "chrome", + feature = "chrome_remote_cache" + ))] + pub cache: bool, + /// Skip browser rendering entirely if cached response exists. + /// When enabled, returns cached HTML directly without launching Chrome. + #[cfg(any( + feature = "cache_request", + feature = "chrome", + feature = "chrome_remote_cache" + ))] + pub cache_skip_browser: bool, + #[cfg(feature = "chrome")] + /// Enable or disable service workers. Enabled by default. + pub service_worker_enabled: bool, + #[cfg(feature = "chrome")] + /// Overrides default host system timezone with the specified one. + #[cfg(feature = "chrome")] + pub timezone_id: Option>, + /// Overrides default host system locale with the specified one. + #[cfg(feature = "chrome")] + pub locale: Option>, + /// Set a custom script to eval on each new document. + #[cfg(feature = "chrome")] + pub evaluate_on_new_document: Option>, + #[cfg(feature = "chrome")] + /// Dismiss dialogs. + pub dismiss_dialogs: Option, + #[cfg(feature = "chrome")] + /// Wait for options for the page. + pub wait_for: Option, + #[cfg(feature = "chrome")] + /// Take a screenshot of the page. + pub screenshot: Option, + #[cfg(feature = "chrome")] + /// Track the events made via chrome. + pub track_events: Option, + #[cfg(feature = "chrome")] + /// Setup fingerprint ID on each document. This does nothing without the flag `chrome` enabled. + pub fingerprint: Fingerprint, + #[cfg(feature = "chrome")] + /// The chrome connection url. Useful for targeting different headless instances. Defaults to using the env CHROME_URL. + pub chrome_connection_url: Option, + /// Scripts to execute for individual pages, the full path of the url is required for an exact match. This is useful for running one off JS on pages like performing custom login actions. + #[cfg(feature = "chrome")] + pub execution_scripts: Option, + /// Web automation scripts to run up to a duration of 60 seconds. + #[cfg(feature = "chrome")] + pub automation_scripts: Option, + /// Setup network interception for request. This does nothing without the flag `chrome_intercept` enabled. + #[cfg(feature = "chrome")] + pub chrome_intercept: RequestInterceptConfiguration, + /// The referer to use. + pub referer: Option, + /// Determine the max bytes per page. + pub max_page_bytes: Option, + /// Determine the max bytes per browser context. + pub max_bytes_allowed: Option, + #[cfg(feature = "chrome")] + /// Disables log domain, prevents further log entries from being reported to the client. This does nothing without the flag `chrome` enabled. + pub disable_log: bool, + #[cfg(feature = "chrome")] + /// Automatic locale and timezone handling via third party. This does nothing without the flag `chrome` enabled. + pub auto_geolocation: bool, + /// The cache policy to use. + pub cache_policy: Option, + #[cfg(feature = "chrome")] + /// Enables bypassing CSP. This does nothing without the flag `chrome` enabled. + pub bypass_csp: bool, + /// Bind the connections only on the network interface. + pub network_interface: Option, + /// Bind to a local IP Address. + pub local_address: Option, + /// The default http connect timeout + pub default_http_connect_timeout: Option, + /// The default http read timeout + pub default_http_read_timeout: Option, + #[cfg(feature = "webdriver")] + /// WebDriver configuration for browser automation. This does nothing without the `webdriver` flag enabled. + pub webdriver_config: Option>, + #[cfg(feature = "search")] + /// Search provider configuration for web search integration. This does nothing without the `search` flag enabled. + pub search_config: Option>, + #[cfg(feature = "spider_cloud")] + /// Spider Cloud config. See . + pub spider_cloud: Option>, + #[cfg(feature = "hedge")] + /// Hedged request configuration for work-stealing on slow requests. + /// When enabled, fires a duplicate request on a different proxy after a delay. + pub hedge: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +/// Serializable HTTP headers. +pub struct SerializableHeaderMap(pub HeaderMap); + +impl SerializableHeaderMap { + /// Innter HeaderMap. + pub fn inner(&self) -> &HeaderMap { + &self.0 + } + /// Returns true if the map contains a value for the specified key. + pub fn contains_key(&self, key: K) -> bool + where + K: AsHeaderName, + { + self.0.contains_key(key) + } + /// Inserts a key-value pair into the map. + pub fn insert( + &mut self, + key: K, + val: reqwest::header::HeaderValue, + ) -> Option + where + K: IntoHeaderName, + { + self.0.insert(key, val) + } + /// Extend a `HeaderMap` with the contents of another `HeaderMap`. + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, HeaderValue)>, + { + self.0.extend(iter); + } +} + +/// Get a cloned copy of the `Referer` header as a `String` (if it exists and is valid UTF-8). +pub fn get_referer(header_map: &Option>) -> Option { + match header_map { + Some(header_map) => { + header_map + .0 + .get(crate::client::header::REFERER) // Retrieves the "Referer" HeaderValue if it exists + .and_then(|value| value.to_str().ok()) // &str from HeaderValue + .map(String::from) // Convert &str to String (owned) + } + _ => None, + } +} + +impl From for SerializableHeaderMap { + fn from(header_map: HeaderMap) -> Self { + SerializableHeaderMap(header_map) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SerializableHeaderMap { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let map: std::collections::BTreeMap = self + .0 + .iter() + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + map.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SerializableHeaderMap { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use reqwest::header::{HeaderName, HeaderValue}; + use std::collections::BTreeMap; + let map: BTreeMap = BTreeMap::deserialize(deserializer)?; + let mut headers = HeaderMap::with_capacity(map.len()); + for (k, v) in map { + let key = HeaderName::from_bytes(k.as_bytes()).map_err(serde::de::Error::custom)?; + let value = HeaderValue::from_str(&v).map_err(serde::de::Error::custom)?; + headers.insert(key, value); + } + Ok(SerializableHeaderMap(headers)) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for AllowListSet { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[cfg(not(feature = "regex"))] + { + self.0.serialize(serializer) + } + + #[cfg(feature = "regex")] + { + self.0 + .patterns() + .iter() + .collect::>() + .serialize(serializer) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for AllowListSet { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[cfg(not(feature = "regex"))] + { + let vec = Vec::::deserialize(deserializer)?; + Ok(AllowListSet(vec)) + } + + #[cfg(feature = "regex")] + { + let patterns = Vec::::deserialize(deserializer)?; + let regex_set = regex::RegexSet::new(&patterns).map_err(serde::de::Error::custom)?; + Ok(AllowListSet(regex_set.into())) + } + } +} + +/// Get the user agent from the top agent list randomly. +#[cfg(feature = "ua_generator")] +pub fn get_ua(chrome: bool) -> &'static str { + if chrome { + ua_generator::ua::spoof_chrome_ua() + } else { + ua_generator::ua::spoof_ua() + } +} + +/// Get the user agent via cargo package + version. +#[cfg(not(feature = "ua_generator"))] +pub fn get_ua(_chrome: bool) -> &'static str { + use std::env; + + lazy_static! { + static ref AGENT: &'static str = + concat!(env!("CARGO_PKG_NAME"), '/', env!("CARGO_PKG_VERSION")); + }; + + AGENT.as_ref() +} + +impl Configuration { + /// Represents crawl configuration for a website. + #[cfg(not(feature = "chrome"))] + pub fn new() -> Self { + Self { + delay: 0, + depth: 25, + redirect_limit: 7, + request_timeout: Some(Duration::from_secs(120)), + only_html: true, + modify_headers: true, + ..Default::default() + } + } + + /// Represents crawl configuration for a website. + #[cfg(feature = "chrome")] + pub fn new() -> Self { + Self { + delay: 0, + depth: 25, + redirect_limit: 7, + request_timeout: Some(Duration::from_secs(120)), + chrome_intercept: RequestInterceptConfiguration::new(cfg!( + feature = "chrome_intercept" + )), + user_agent: Some(Box::new(get_ua(true).into())), + only_html: true, + cache: true, + modify_headers: true, + service_worker_enabled: true, + fingerprint: Fingerprint::Basic, + auto_geolocation: false, + ..Default::default() + } + } + + /// Build a `RemoteMultimodalEngine` from `RemoteMultimodalConfigs`. + /// Requires the `agent` feature. + #[cfg(feature = "agent")] + pub fn build_remote_multimodal_engine( + &self, + ) -> Option { + let cfgs = self.remote_multimodal.as_ref()?; + let sem = cfgs + .concurrency_limit + .filter(|&n| n > 0) + .map(|n| std::sync::Arc::new(tokio::sync::Semaphore::new(n))); + + #[allow(unused_mut)] + let mut engine = crate::features::automation::RemoteMultimodalEngine::new( + cfgs.api_url.clone(), + cfgs.model_name.clone(), + cfgs.system_prompt.clone(), + ) + .with_api_key(cfgs.api_key.as_deref()) + .with_system_prompt_extra(cfgs.system_prompt_extra.as_deref()) + .with_user_message_extra(cfgs.user_message_extra.as_deref()) + .with_remote_multimodal_config(cfgs.cfg.clone()) + .with_prompt_url_gate(cfgs.prompt_url_gate.clone()) + .with_vision_model(cfgs.vision_model.clone()) + .with_text_model(cfgs.text_model.clone()) + .with_vision_route_mode(cfgs.vision_route_mode) + .with_chrome_ai(cfgs.use_chrome_ai) + .with_semaphore(sem) + .to_owned(); + + #[cfg(feature = "agent_skills")] + if let Some(ref registry) = cfgs.skill_registry { + engine.with_skill_registry(Some(registry.clone())); + } + + // Build per-round complexity router from model pool (3+ models required) + let model_pool = cfgs.model_pool.clone(); + if model_pool.len() >= 3 { + let model_names: Vec<&str> = + model_pool.iter().map(|ep| ep.model_name.as_str()).collect(); + let policy = crate::features::automation::auto_policy(&model_names); + engine.model_router = Some(crate::features::automation::ModelRouter::with_policy( + policy, + )); + } + engine.model_pool = model_pool; + + Some(engine) + } + + /// Determine if the agent should be set to a Chrome Agent. + #[cfg(not(feature = "chrome"))] + pub(crate) fn only_chrome_agent(&self) -> bool { + false + } + + /// Determine if the agent should be set to a Chrome Agent. + #[cfg(feature = "chrome")] + pub(crate) fn only_chrome_agent(&self) -> bool { + self.chrome_connection_url.is_some() + || self.wait_for.is_some() + || self.chrome_intercept.enabled + || self.stealth_mode.stealth() + || self.fingerprint.valid() + } + + #[cfg(feature = "regex")] + /// Compile the regex for the blacklist. + pub fn get_blacklist(&self) -> Box { + match &self.blacklist_url { + Some(blacklist) => match regex::RegexSet::new(&**blacklist) { + Ok(s) => Box::new(s), + _ => Default::default(), + }, + _ => Default::default(), + } + } + + #[cfg(not(feature = "regex"))] + /// Handle the blacklist options. + pub fn get_blacklist(&self) -> AllowList { + match &self.blacklist_url { + Some(blacklist) => blacklist.to_owned(), + _ => Default::default(), + } + } + + /// Set the blacklist + pub(crate) fn set_blacklist(&mut self) { + self.blacklist = AllowListSet(self.get_blacklist()); + } + + /// Set the whitelist + pub fn set_whitelist(&mut self) { + self.whitelist = AllowListSet(self.get_whitelist()); + } + + /// Configure the allow list. + pub fn configure_allowlist(&mut self) { + self.set_whitelist(); + self.set_blacklist(); + } + + /// Get the blacklist compiled. + pub fn get_blacklist_compiled(&self) -> &AllowList { + &self.blacklist.0 + } + + /// Setup the budget for crawling. + pub fn configure_budget(&mut self) { + self.inner_budget.clone_from(&self.budget); + } + + /// Get the whitelist compiled. + pub fn get_whitelist_compiled(&self) -> &AllowList { + &self.whitelist.0 + } + + #[cfg(feature = "regex")] + /// Compile the regex for the whitelist. + pub fn get_whitelist(&self) -> Box { + match &self.whitelist_url { + Some(whitelist) => match regex::RegexSet::new(&**whitelist) { + Ok(s) => Box::new(s), + _ => Default::default(), + }, + _ => Default::default(), + } + } + + #[cfg(not(feature = "regex"))] + /// Handle the whitelist options. + pub fn get_whitelist(&self) -> AllowList { + match &self.whitelist_url { + Some(whitelist) => whitelist.to_owned(), + _ => Default::default(), + } + } + + #[cfg(feature = "sitemap")] + /// Add sitemap paths to the whitelist and track what was added. + pub fn add_sitemap_to_whitelist(&mut self) -> SitemapWhitelistChanges { + let mut changes = SitemapWhitelistChanges::default(); + + if self.ignore_sitemap && self.whitelist_url.is_none() { + return changes; + } + + if let Some(list) = self.whitelist_url.as_mut() { + if list.is_empty() { + return changes; + } + + let default = CompactString::from("sitemap.xml"); + + if !list.contains(&default) { + list.push(default); + changes.added_default = true; + } + + if let Some(custom) = &self.sitemap_url { + if !list.contains(custom) { + list.push(*custom.clone()); + changes.added_custom = true; + } + } + } + + changes + } + + #[cfg(feature = "sitemap")] + /// Revert any changes made to the whitelist by `add_sitemap_to_whitelist`. + pub fn remove_sitemap_from_whitelist(&mut self, changes: SitemapWhitelistChanges) { + if let Some(list) = self.whitelist_url.as_mut() { + if changes.added_default { + let default = CompactString::from("sitemap.xml"); + if let Some(pos) = list.iter().position(|s| s == default) { + list.remove(pos); + } + } + if changes.added_custom { + if let Some(custom) = &self.sitemap_url { + if let Some(pos) = list.iter().position(|s| *s == **custom) { + list.remove(pos); + } + } + } + if list.is_empty() { + self.whitelist_url = None; + } + } + } + + /// Respect robots.txt file. + pub fn with_respect_robots_txt(&mut self, respect_robots_txt: bool) -> &mut Self { + self.respect_robots_txt = respect_robots_txt; + self + } + + /// Include subdomains detection. + pub fn with_subdomains(&mut self, subdomains: bool) -> &mut Self { + self.subdomains = subdomains; + self + } + + /// Bypass CSP protection detection. This does nothing without the feat flag `chrome` enabled. + #[cfg(feature = "chrome")] + pub fn with_csp_bypass(&mut self, enabled: bool) -> &mut Self { + self.bypass_csp = enabled; + self + } + + /// Bypass CSP protection detection. This does nothing without the feat flag `chrome` enabled. + #[cfg(not(feature = "chrome"))] + pub fn with_csp_bypass(&mut self, _enabled: bool) -> &mut Self { + self + } + + /// Bind the connections only on the network interface. + pub fn with_network_interface(&mut self, network_interface: Option) -> &mut Self { + self.network_interface = network_interface; + self + } + + /// Bind to a local IP Address. + pub fn with_local_address(&mut self, local_address: Option) -> &mut Self { + self.local_address = local_address; + self + } + + /// Include tld detection. + pub fn with_tld(&mut self, tld: bool) -> &mut Self { + self.tld = tld; + self + } + + /// The max duration for the crawl. This is useful when websites use a robots.txt with long durations and throttle the timeout removing the full concurrency. + pub fn with_crawl_timeout(&mut self, crawl_timeout: Option) -> &mut Self { + self.crawl_timeout = crawl_timeout; + self + } + + /// Delay between request as ms. + pub fn with_delay(&mut self, delay: u64) -> &mut Self { + self.delay = delay; + self + } + + /// Only use HTTP/2. + pub fn with_http2_prior_knowledge(&mut self, http2_prior_knowledge: bool) -> &mut Self { + self.http2_prior_knowledge = http2_prior_knowledge; + self + } + + /// Max time to wait for request. By default request times out in 15s. Set to None to disable. + pub fn with_request_timeout(&mut self, request_timeout: Option) -> &mut Self { + match request_timeout { + Some(timeout) => self.request_timeout = Some(timeout), + _ => self.request_timeout = None, + }; + + self + } + + #[cfg(feature = "sitemap")] + /// Set the sitemap url. This does nothing without the `sitemap` feature flag. + pub fn with_sitemap(&mut self, sitemap_url: Option<&str>) -> &mut Self { + match sitemap_url { + Some(sitemap_url) => { + self.sitemap_url = Some(CompactString::new(sitemap_url.to_string()).into()) + } + _ => self.sitemap_url = None, + }; + self + } + + #[cfg(not(feature = "sitemap"))] + /// Set the sitemap url. This does nothing without the `sitemap` feature flag. + pub fn with_sitemap(&mut self, _sitemap_url: Option<&str>) -> &mut Self { + self + } + + #[cfg(feature = "sitemap")] + /// Ignore the sitemap when crawling. This method does nothing if the `sitemap` is not enabled. + pub fn with_ignore_sitemap(&mut self, ignore_sitemap: bool) -> &mut Self { + self.ignore_sitemap = ignore_sitemap; + self + } + + #[cfg(not(feature = "sitemap"))] + /// Ignore the sitemap when crawling. This method does nothing if the `sitemap` is not enabled. + pub fn with_ignore_sitemap(&mut self, _ignore_sitemap: bool) -> &mut Self { + self + } + + /// Add user agent to request. + pub fn with_user_agent(&mut self, user_agent: Option<&str>) -> &mut Self { + match user_agent { + Some(agent) => self.user_agent = Some(CompactString::new(agent).into()), + _ => self.user_agent = None, + }; + self + } + + /// Preserve the HOST header. + pub fn with_preserve_host_header(&mut self, preserve: bool) -> &mut Self { + self.preserve_host_header = preserve; + self + } + + /// Use a remote multimodal model to drive browser automation. + /// Requires the `agent` feature. + #[cfg(feature = "agent")] + pub fn with_remote_multimodal( + &mut self, + remote_multimodal: Option, + ) -> &mut Self { + self.remote_multimodal = remote_multimodal.map(Box::new); + self + } + + /// Use a remote multimodal model to drive browser automation. + /// When the `agent` feature is not enabled, this uses a stub type. + #[cfg(not(feature = "agent"))] + pub fn with_remote_multimodal( + &mut self, + remote_multimodal: Option, + ) -> &mut Self { + self.remote_multimodal = remote_multimodal.map(Box::new); + self + } + + #[cfg(not(feature = "openai"))] + /// The OpenAI configs to use to drive the browser. This method does nothing if the `openai` is not enabled. + pub fn with_openai(&mut self, _openai_config: Option) -> &mut Self { + self + } + + /// The OpenAI configs to use to drive the browser. This method does nothing if the `openai` is not enabled. + #[cfg(feature = "openai")] + pub fn with_openai(&mut self, openai_config: Option) -> &mut Self { + match openai_config { + Some(openai_config) => self.openai_config = Some(Box::new(openai_config)), + _ => self.openai_config = None, + }; + self + } + + #[cfg(not(feature = "gemini"))] + /// The Gemini configs to use to drive the browser. This method does nothing if the `gemini` is not enabled. + pub fn with_gemini(&mut self, _gemini_config: Option) -> &mut Self { + self + } + + /// The Gemini configs to use to drive the browser. This method does nothing if the `gemini` is not enabled. + #[cfg(feature = "gemini")] + pub fn with_gemini(&mut self, gemini_config: Option) -> &mut Self { + match gemini_config { + Some(gemini_config) => self.gemini_config = Some(Box::new(gemini_config)), + _ => self.gemini_config = None, + }; + self + } + + #[cfg(feature = "cookies")] + /// Cookie string to use in request. This does nothing without the `cookies` flag enabled. + pub fn with_cookies(&mut self, cookie_str: &str) -> &mut Self { + self.cookie_str = cookie_str.into(); + self + } + + #[cfg(not(feature = "cookies"))] + /// Cookie string to use in request. This does nothing without the `cookies` flag enabled. + pub fn with_cookies(&mut self, _cookie_str: &str) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Set custom fingerprint ID for request. This does nothing without the `chrome` flag enabled. + pub fn with_fingerprint(&mut self, fingerprint: bool) -> &mut Self { + if fingerprint { + self.fingerprint = Fingerprint::Basic; + } else { + self.fingerprint = Fingerprint::None; + } + self + } + + #[cfg(feature = "chrome")] + /// Set custom fingerprint ID for request. This does nothing without the `chrome` flag enabled. + pub fn with_fingerprint_advanced(&mut self, fingerprint: Fingerprint) -> &mut Self { + self.fingerprint = fingerprint; + self + } + + #[cfg(not(feature = "chrome"))] + /// Set custom fingerprint ID for request. This does nothing without the `chrome` flag enabled. + pub fn with_fingerprint(&mut self, _fingerprint: bool) -> &mut Self { + self + } + + /// Use proxies for request. + pub fn with_proxies(&mut self, proxies: Option>) -> &mut Self { + self.proxies = proxies.map(|p| { + p.iter() + .map(|addr| RequestProxy { + addr: addr.to_owned(), + ..Default::default() + }) + .collect::>() + }); + self + } + + /// Use proxies for request with control between chrome and http. + pub fn with_proxies_direct(&mut self, proxies: Option>) -> &mut Self { + self.proxies = proxies; + self + } + + /// Use a shared semaphore to evenly handle workloads. The default is false. + pub fn with_shared_queue(&mut self, shared_queue: bool) -> &mut Self { + self.shared_queue = shared_queue; + self + } + + /// Add blacklist urls to ignore. + pub fn with_blacklist_url(&mut self, blacklist_url: Option>) -> &mut Self + where + Vec: From>, + { + match blacklist_url { + Some(p) => self.blacklist_url = Some(p.into()), + _ => self.blacklist_url = None, + }; + self + } + + /// Add whitelist urls to allow. + pub fn with_whitelist_url(&mut self, whitelist_url: Option>) -> &mut Self + where + Vec: From>, + { + match whitelist_url { + Some(p) => self.whitelist_url = Some(p.into()), + _ => self.whitelist_url = None, + }; + self + } + + /// Return the links found on the page in the channel subscriptions. This method does nothing if the `decentralized` is enabled. + pub fn with_return_page_links(&mut self, return_page_links: bool) -> &mut Self { + self.return_page_links = return_page_links; + self + } + + /// Set HTTP headers for request using [reqwest::header::HeaderMap](https://docs.rs/reqwest/latest/reqwest/header/struct.HeaderMap.html). + pub fn with_headers(&mut self, headers: Option) -> &mut Self { + match headers { + Some(m) => self.headers = Some(SerializableHeaderMap::from(m).into()), + _ => self.headers = None, + }; + self + } + + /// Set the max redirects allowed for request. + pub fn with_redirect_limit(&mut self, redirect_limit: usize) -> &mut Self { + self.redirect_limit = redirect_limit; + self + } + + /// Set the redirect policy to use. + pub fn with_redirect_policy(&mut self, policy: RedirectPolicy) -> &mut Self { + self.redirect_policy = policy; + self + } + + /// Add a referer (mis-spelling) to the request. + pub fn with_referer(&mut self, referer: Option) -> &mut Self { + self.referer = referer; + self + } + + /// Add a referer to the request. + pub fn with_referrer(&mut self, referer: Option) -> &mut Self { + self.referer = referer; + self + } + + /// Determine whether to collect all the resources found on pages. + pub fn with_full_resources(&mut self, full_resources: bool) -> &mut Self { + self.full_resources = full_resources; + self + } + + /// Determine whether to dismiss dialogs. This method does nothing if the `chrome` is enabled. + #[cfg(feature = "chrome")] + pub fn with_dismiss_dialogs(&mut self, dismiss_dialogs: bool) -> &mut Self { + self.dismiss_dialogs = Some(dismiss_dialogs); + self + } + + /// Determine whether to dismiss dialogs. This method does nothing if the `chrome` is enabled. + #[cfg(not(feature = "chrome"))] + pub fn with_dismiss_dialogs(&mut self, _dismiss_dialogs: bool) -> &mut Self { + self + } + + /// Set the request emuluation. This method does nothing if the `wreq` flag is not enabled. + #[cfg(feature = "wreq")] + pub fn with_emulation(&mut self, emulation: Option) -> &mut Self { + self.emulation = emulation; + self + } + + #[cfg(feature = "cron")] + /// Setup cron jobs to run. This does nothing without the `cron` flag enabled. + pub fn with_cron(&mut self, cron_str: &str, cron_type: CronType) -> &mut Self { + self.cron_str = cron_str.into(); + self.cron_type = cron_type; + self + } + + #[cfg(not(feature = "cron"))] + /// Setup cron jobs to run. This does nothing without the `cron` flag enabled. + pub fn with_cron(&mut self, _cron_str: &str, _cron_type: CronType) -> &mut Self { + self + } + + /// Set a crawl page limit. If the value is 0 there is no limit. + pub fn with_limit(&mut self, limit: u32) -> &mut Self { + self.with_budget(Some(hashbrown::HashMap::from([("*", limit)]))); + self + } + + /// Set the concurrency limits. If you set the value to None to use the default limits using the system CPU cors * n. + pub fn with_concurrency_limit(&mut self, limit: Option) -> &mut Self { + self.concurrency_limit = limit; + self + } + + #[cfg(feature = "chrome")] + /// Set the authentiation challenge response. This does nothing without the feat flag `chrome` enabled. + pub fn with_auth_challenge_response( + &mut self, + auth_challenge_response: Option, + ) -> &mut Self { + self.auth_challenge_response = auth_challenge_response; + self + } + + #[cfg(feature = "chrome")] + /// Set a custom script to evaluate on new document creation. This does nothing without the feat flag `chrome` enabled. + pub fn with_evaluate_on_new_document( + &mut self, + evaluate_on_new_document: Option>, + ) -> &mut Self { + self.evaluate_on_new_document = evaluate_on_new_document; + self + } + + #[cfg(not(feature = "chrome"))] + /// Set a custom script to evaluate on new document creation. This does nothing without the feat flag `chrome` enabled. + pub fn with_evaluate_on_new_document( + &mut self, + _evaluate_on_new_document: Option>, + ) -> &mut Self { + self + } + + #[cfg(not(feature = "chrome"))] + /// Set the authentiation challenge response. This does nothing without the feat flag `chrome` enabled. + pub fn with_auth_challenge_response( + &mut self, + _auth_challenge_response: Option, + ) -> &mut Self { + self + } + + /// Set a crawl depth limit. If the value is 0 there is no limit. + pub fn with_depth(&mut self, depth: usize) -> &mut Self { + self.depth = depth; + self + } + + #[cfg(any(feature = "cache_request", feature = "chrome_remote_cache"))] + /// Cache the page following HTTP rules. This method does nothing if the `cache` feature is not enabled. + pub fn with_caching(&mut self, cache: bool) -> &mut Self { + self.cache = cache; + self + } + + #[cfg(not(any(feature = "cache_request", feature = "chrome_remote_cache")))] + /// Cache the page following HTTP rules. This method does nothing if the `cache` feature is not enabled. + pub fn with_caching(&mut self, _cache: bool) -> &mut Self { + self + } + + #[cfg(any(feature = "cache_request", feature = "chrome_remote_cache"))] + /// Skip browser rendering entirely if cached response exists. + /// When enabled with caching, returns cached HTML directly without launching Chrome. + /// This is useful for performance when you only need the cached content. + pub fn with_cache_skip_browser(&mut self, skip: bool) -> &mut Self { + self.cache_skip_browser = skip; + self + } + + #[cfg(not(any(feature = "cache_request", feature = "chrome_remote_cache")))] + /// Skip browser rendering entirely if cached response exists. + /// This method does nothing if the cache features are not enabled. + pub fn with_cache_skip_browser(&mut self, _skip: bool) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Enable or disable Service Workers. This method does nothing if the `chrome` feature is not enabled. + pub fn with_service_worker_enabled(&mut self, enabled: bool) -> &mut Self { + self.service_worker_enabled = enabled; + self + } + + #[cfg(not(feature = "chrome"))] + /// Enable or disable Service Workers. This method does nothing if the `chrome` feature is not enabled. + pub fn with_service_worker_enabled(&mut self, _enabled: bool) -> &mut Self { + self + } + + /// Automatically setup geo-location configurations when using a proxy. This method does nothing if the `chrome` feature is not enabled. + #[cfg(not(feature = "chrome"))] + pub fn with_auto_geolocation(&mut self, _enabled: bool) -> &mut Self { + self + } + + /// Automatically setup geo-location configurations when using a proxy. This method does nothing if the `chrome` feature is not enabled. + #[cfg(feature = "chrome")] + pub fn with_auto_geolocation(&mut self, enabled: bool) -> &mut Self { + self.auto_geolocation = enabled; + self + } + + /// Set the retry limit for request. Set the value to 0 for no retries. The default is 0. + pub fn with_retry(&mut self, retry: u8) -> &mut Self { + self.retry = retry; + self + } + + /// The default http connect timeout. + pub fn with_default_http_connect_timeout( + &mut self, + default_http_connect_timeout: Option, + ) -> &mut Self { + self.default_http_connect_timeout = default_http_connect_timeout; + self + } + + /// The default http read timeout. + pub fn with_default_http_read_timeout( + &mut self, + default_http_read_timeout: Option, + ) -> &mut Self { + self.default_http_read_timeout = default_http_read_timeout; + self + } + + /// Skip setting up a control thread for pause, start, and shutdown programmatic handling. This does nothing without the 'control' flag enabled. + pub fn with_no_control_thread(&mut self, no_control_thread: bool) -> &mut Self { + self.no_control_thread = no_control_thread; + self + } + + /// Configures the viewport of the browser, which defaults to 800x600. This method does nothing if the 'chrome' feature is not enabled. + pub fn with_viewport(&mut self, viewport: Option) -> &mut Self { + self.viewport = viewport.map(|vp| vp); + self + } + + #[cfg(feature = "chrome")] + /// Use stealth mode for the request. This does nothing without the `chrome` flag enabled. + pub fn with_stealth(&mut self, stealth_mode: bool) -> &mut Self { + if stealth_mode { + self.stealth_mode = spider_fingerprint::configs::Tier::Basic; + } else { + self.stealth_mode = spider_fingerprint::configs::Tier::None; + } + self + } + + #[cfg(feature = "chrome")] + /// Use stealth mode for the request. This does nothing without the `chrome` flag enabled. + pub fn with_stealth_advanced( + &mut self, + stealth_mode: spider_fingerprint::configs::Tier, + ) -> &mut Self { + self.stealth_mode = stealth_mode; + self + } + + #[cfg(not(feature = "chrome"))] + /// Use stealth mode for the request. This does nothing without the `chrome` flag enabled. + pub fn with_stealth(&mut self, _stealth_mode: bool) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Wait for network request to be idle within a time frame period (500ms no network connections). This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_idle_network( + &mut self, + wait_for_idle_network: Option, + ) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.idle_network = wait_for_idle_network, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.idle_network = wait_for_idle_network; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(feature = "chrome")] + /// Wait for network request with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_idle_network0( + &mut self, + wait_for_idle_network0: Option, + ) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.idle_network0 = wait_for_idle_network0, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.idle_network0 = wait_for_idle_network0; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(feature = "chrome")] + /// Wait for network to be almost idle with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_almost_idle_network0( + &mut self, + wait_for_almost_idle_network0: Option, + ) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.almost_idle_network0 = wait_for_almost_idle_network0, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.almost_idle_network0 = wait_for_almost_idle_network0; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for network to be almost idle with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_almost_idle_network0( + &mut self, + _wait_for_almost_idle_network0: Option, + ) -> &mut Self { + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for network request with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_idle_network0( + &mut self, + _wait_for_idle_network0: Option, + ) -> &mut Self { + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for idle network request. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_idle_network( + &mut self, + _wait_for_idle_network: Option, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Wait for idle dom mutations for target element. This method does nothing if the [chrome] feature is not enabled. + pub fn with_wait_for_idle_dom( + &mut self, + wait_for_idle_dom: Option, + ) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.dom = wait_for_idle_dom, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.dom = wait_for_idle_dom; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for idle dom mutations for target element. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_idle_dom( + &mut self, + _wait_for_idle_dom: Option, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Wait for a selector. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_selector( + &mut self, + wait_for_selector: Option, + ) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.selector = wait_for_selector, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.selector = wait_for_selector; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for a selector. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_selector( + &mut self, + _wait_for_selector: Option, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Wait for with delay. Should only be used for testing. This method does nothing if the 'chrome' feature is not enabled. + pub fn with_wait_for_delay(&mut self, wait_for_delay: Option) -> &mut Self { + match self.wait_for.as_mut() { + Some(wait_for) => wait_for.delay = wait_for_delay, + _ => { + let mut wait_for = WaitFor::default(); + wait_for.delay = wait_for_delay; + self.wait_for = Some(wait_for); + } + } + self + } + + #[cfg(not(feature = "chrome"))] + /// Wait for with delay. Should only be used for testing. This method does nothing if the 'chrome' feature is not enabled. + pub fn with_wait_for_delay(&mut self, _wait_for_delay: Option) -> &mut Self { + self + } + + #[cfg(feature = "chrome_intercept")] + /// Use request intercept for the request to only allow content that matches the host. If the content is from a 3rd party it needs to be part of our include list. This method does nothing if the `chrome_intercept` is not enabled. + pub fn with_chrome_intercept( + &mut self, + chrome_intercept: RequestInterceptConfiguration, + url: &Option>, + ) -> &mut Self { + self.chrome_intercept = chrome_intercept; + self.chrome_intercept.setup_intercept_manager(url); + self + } + + #[cfg(not(feature = "chrome_intercept"))] + /// Use request intercept for the request to only allow content required for the page that matches the host. If the content is from a 3rd party it needs to be part of our include list. This method does nothing if the `chrome_intercept` is not enabled. + pub fn with_chrome_intercept( + &mut self, + _chrome_intercept: RequestInterceptConfiguration, + _url: &Option>, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Set the connection url for the chrome instance. This method does nothing if the `chrome` is not enabled. + pub fn with_chrome_connection(&mut self, chrome_connection_url: Option) -> &mut Self { + self.chrome_connection_url = chrome_connection_url; + self + } + + #[cfg(not(feature = "chrome"))] + /// Set the connection url for the chrome instance. This method does nothing if the `chrome` is not enabled. + pub fn with_chrome_connection(&mut self, _chrome_connection_url: Option) -> &mut Self { + self + } + + #[cfg(not(feature = "chrome"))] + /// Set JS to run on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_execution_scripts( + &mut self, + _execution_scripts: Option, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Set JS to run on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_execution_scripts( + &mut self, + execution_scripts: Option, + ) -> &mut Self { + self.execution_scripts = + crate::features::chrome_common::convert_to_trie_execution_scripts(&execution_scripts); + self + } + + #[cfg(not(feature = "chrome"))] + /// Run web automated actions on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_automation_scripts( + &mut self, + _automation_scripts: Option, + ) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Run web automated actions on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_automation_scripts( + &mut self, + automation_scripts: Option, + ) -> &mut Self { + self.automation_scripts = + crate::features::chrome_common::convert_to_trie_automation_scripts(&automation_scripts); + self + } + + /// Set a crawl budget per path with levels support /a/b/c or for all paths with "*". This does nothing without the `budget` flag enabled. + pub fn with_budget(&mut self, budget: Option>) -> &mut Self { + self.budget = match budget { + Some(budget) => { + let mut crawl_budget: hashbrown::HashMap< + case_insensitive_string::CaseInsensitiveString, + u32, + > = hashbrown::HashMap::new(); + + for b in budget.into_iter() { + crawl_budget.insert( + case_insensitive_string::CaseInsensitiveString::from(b.0), + b.1, + ); + } + + Some(crawl_budget) + } + _ => None, + }; + self + } + + /// Group external domains to treat the crawl as one. If None is passed this will clear all prior domains. + pub fn with_external_domains<'a, 'b>( + &mut self, + external_domains: Option + 'a>, + ) -> &mut Self { + match external_domains { + Some(external_domains) => { + self.external_domains_caseless = external_domains + .into_iter() + .filter_map(|d| { + if d == "*" { + Some("*".into()) + } else { + let host = get_domain_from_url(&d); + + if !host.is_empty() { + Some(host.into()) + } else { + None + } + } + }) + .collect::>() + .into(); + } + _ => self.external_domains_caseless = Default::default(), + } + + self + } + + /// Dangerously accept invalid certificates - this should be used as a last resort. + pub fn with_danger_accept_invalid_certs(&mut self, accept_invalid_certs: bool) -> &mut Self { + self.accept_invalid_certs = accept_invalid_certs; + self + } + + /// Normalize the content de-duplicating trailing slash pages and other pages that can be duplicated. This may initially show the link in your links_visited or subscription calls but, the following links will not be crawled. + pub fn with_normalize(&mut self, normalize: bool) -> &mut Self { + self.normalize = normalize; + self + } + + #[cfg(not(feature = "disk"))] + /// Store all the links found on the disk to share the state. This does nothing without the `disk` flag enabled. + pub fn with_shared_state(&mut self, _shared: bool) -> &mut Self { + self + } + + /// Store all the links found on the disk to share the state. This does nothing without the `disk` flag enabled. + #[cfg(feature = "disk")] + pub fn with_shared_state(&mut self, shared: bool) -> &mut Self { + self.shared = shared; + self + } + + #[cfg(not(feature = "chrome"))] + /// Overrides default host system timezone with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_timezone_id(&mut self, _timezone_id: Option) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Overrides default host system timezone with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_timezone_id(&mut self, timezone_id: Option) -> &mut Self { + self.timezone_id = timezone_id.map(|timezone_id| timezone_id.into()); + self + } + + #[cfg(not(feature = "chrome"))] + /// Overrides default host system locale with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_locale(&mut self, _locale: Option) -> &mut Self { + self + } + + #[cfg(feature = "chrome")] + /// Overrides default host system locale with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_locale(&mut self, locale: Option) -> &mut Self { + self.locale = locale.map(|locale| locale.into()); + self + } + + #[cfg(feature = "chrome")] + /// Track the events made via chrome. + pub fn with_event_tracker(&mut self, track_events: Option) -> &mut Self { + self.track_events = track_events; + self + } + + /// Set the chrome screenshot configuration. This does nothing without the `chrome` flag enabled. + #[cfg(not(feature = "chrome"))] + pub fn with_screenshot(&mut self, _screenshot_config: Option) -> &mut Self { + self + } + + /// Set the chrome screenshot configuration. This does nothing without the `chrome` flag enabled. + #[cfg(feature = "chrome")] + pub fn with_screenshot(&mut self, screenshot_config: Option) -> &mut Self { + self.screenshot = screenshot_config; + self + } + + /// Set the max amount of bytes to collect per page. This method does nothing if the `chrome` is not enabled. + pub fn with_max_page_bytes(&mut self, max_page_bytes: Option) -> &mut Self { + self.max_page_bytes = max_page_bytes; + self + } + + /// Set the max amount of bytes to collected for the browser context. This method does nothing if the `chrome` is not enabled. + pub fn with_max_bytes_allowed(&mut self, max_bytes_allowed: Option) -> &mut Self { + self.max_bytes_allowed = max_bytes_allowed; + self + } + + /// Block assets from loading from the network. + pub fn with_block_assets(&mut self, only_html: bool) -> &mut Self { + self.only_html = only_html; + self + } + + /// Modify the headers to mimic a real browser. + pub fn with_modify_headers(&mut self, modify_headers: bool) -> &mut Self { + self.modify_headers = modify_headers; + self + } + + /// Modify the HTTP client headers to mimic a real browser. + pub fn with_modify_http_client_headers( + &mut self, + modify_http_client_headers: bool, + ) -> &mut Self { + self.modify_http_client_headers = modify_http_client_headers; + self + } + + /// Set the cache policy. + pub fn with_cache_policy(&mut self, cache_policy: Option) -> &mut Self { + self.cache_policy = cache_policy; + self + } + + #[cfg(feature = "webdriver")] + /// Set the WebDriver configuration. This does nothing without the `webdriver` flag enabled. + pub fn with_webdriver_config( + &mut self, + webdriver_config: Option, + ) -> &mut Self { + self.webdriver_config = webdriver_config.map(Box::new); + self + } + + #[cfg(not(feature = "webdriver"))] + /// Set the WebDriver configuration. This does nothing without the `webdriver` flag enabled. + pub fn with_webdriver_config( + &mut self, + _webdriver_config: Option, + ) -> &mut Self { + self + } + + /// Get the cache option to use for the run. This does nothing without the 'cache_request' feature. + #[cfg(any(feature = "cache_request", feature = "chrome_remote_cache"))] + pub(crate) fn get_cache_options(&self) -> Option { + use crate::utils::CacheOptions; + if !self.cache { + return None; + } + let auth_token = self + .headers + .as_ref() + .and_then(|headers| { + headers + .0 + .get("authorization") + .or_else(|| headers.0.get("Authorization")) + }) + .map(|s| s.to_owned()); + + let skip_browser = self.cache_skip_browser; + + match auth_token { + Some(token) if !token.is_empty() => { + if let Ok(token_str) = token.to_str() { + if skip_browser { + Some(CacheOptions::SkipBrowserAuthorized(token_str.into())) + } else { + Some(CacheOptions::Authorized(token_str.into())) + } + } else if skip_browser { + Some(CacheOptions::SkipBrowser) + } else { + Some(CacheOptions::Yes) + } + } + _ => { + if skip_browser { + Some(CacheOptions::SkipBrowser) + } else { + Some(CacheOptions::Yes) + } + } + } + } + + /// Get the cache option to use for the run. This does nothing without the 'cache_request' feature. + #[cfg(all( + feature = "chrome", + not(any(feature = "cache_request", feature = "chrome_remote_cache")) + ))] + pub(crate) fn get_cache_options(&self) -> Option { + None + } + + /// Get the cache option to use for the run when chrome/cache features are disabled. + #[cfg(not(any( + feature = "cache_request", + feature = "chrome_remote_cache", + feature = "chrome" + )))] + #[allow(dead_code)] + pub(crate) fn get_cache_options(&self) -> Option { + None + } + + /// Build the website configuration when using with_builder. + pub fn build(&self) -> Self { + self.to_owned() + } + + #[cfg(feature = "search")] + /// Configure web search integration. This does nothing without the `search` flag enabled. + pub fn with_search_config(&mut self, search_config: Option) -> &mut Self { + self.search_config = search_config.map(Box::new); + self + } + + #[cfg(not(feature = "search"))] + /// Configure web search integration. This does nothing without the `search` flag enabled. + pub fn with_search_config(&mut self, _search_config: Option<()>) -> &mut Self { + self + } + + /// Set a [spider.cloud](https://spider.cloud) API key (Proxy mode). + #[cfg(feature = "spider_cloud")] + pub fn with_spider_cloud(&mut self, api_key: &str) -> &mut Self { + self.spider_cloud = Some(Box::new(SpiderCloudConfig::new(api_key))); + self + } + + /// Set a [spider.cloud](https://spider.cloud) API key (no-op without `spider_cloud` feature). + #[cfg(not(feature = "spider_cloud"))] + pub fn with_spider_cloud(&mut self, _api_key: &str) -> &mut Self { + self + } + + /// Set a [spider.cloud](https://spider.cloud) config. + #[cfg(feature = "spider_cloud")] + pub fn with_spider_cloud_config(&mut self, config: SpiderCloudConfig) -> &mut Self { + self.spider_cloud = Some(Box::new(config)); + self + } + + /// Set a [spider.cloud](https://spider.cloud) config (no-op without `spider_cloud` feature). + #[cfg(not(feature = "spider_cloud"))] + pub fn with_spider_cloud_config(&mut self, _config: ()) -> &mut Self { + self + } + + /// Set the hedged request (work-stealing) configuration. + #[cfg(feature = "hedge")] + pub fn with_hedge(&mut self, config: crate::utils::hedge::HedgeConfig) -> &mut Self { + self.hedge = Some(config); + self + } + + /// Set the hedged request configuration (no-op without `hedge` feature). + #[cfg(not(feature = "hedge"))] + pub fn with_hedge(&mut self, _config: ()) -> &mut Self { + self + } +} + +/// Search provider configuration for web search integration. +#[cfg(feature = "search")] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SearchConfig { + /// The search provider to use. + pub provider: SearchProviderType, + /// API key for the search provider. + pub api_key: String, + /// Custom API URL (overrides default endpoint for the provider). + pub api_url: Option, + /// Default search options. + pub default_options: Option, +} + +#[cfg(feature = "search")] +impl SearchConfig { + /// Create a new search configuration. + pub fn new(provider: SearchProviderType, api_key: impl Into) -> Self { + Self { + provider, + api_key: api_key.into(), + api_url: None, + default_options: None, + } + } + + /// Use a custom API endpoint for this provider. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Set default search options. + pub fn with_default_options(mut self, options: SearchOptions) -> Self { + self.default_options = Some(options); + self + } + + /// Check if this configuration is valid and search is enabled. + /// + /// Returns true if an API key is set or a custom API URL is configured. + pub fn is_enabled(&self) -> bool { + !self.api_key.is_empty() || self.api_url.is_some() + } +} + +/// Available search providers. +#[cfg(feature = "search")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SearchProviderType { + /// Serper.dev - Google SERP API (high quality). + #[default] + Serper, + /// Brave Search API (privacy-focused). + Brave, + /// Microsoft Bing Web Search API. + Bing, + /// Tavily AI Search (optimized for LLMs). + Tavily, +} + +// ─── Spider Cloud ─────────────────────────────────────────────────────────── + +/// Integration mode for [spider.cloud](https://spider.cloud). +#[cfg(feature = "spider_cloud")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SpiderCloudMode { + /// Route all HTTP requests through `proxy.spider.cloud`. + /// This is the simplest mode — the existing fetch pipeline works + /// unmodified, traffic goes through the proxy transparently. + #[default] + Proxy, + /// Use the spider.cloud `POST /crawl` API (with `limit: 1`) for each page. + /// Best for simple scraping needs. + Api, + /// Use the spider.cloud `POST /unblocker` API for anti-bot bypass. + /// Best for hard-to-get pages behind advanced bot protection. + Unblocker, + /// Direct fetch first; fall back to spider.cloud API on + /// 403 / 429 / 503 or connection errors. + Fallback, + /// Intelligent mode: proxy by default, automatically falls back to + /// `/unblocker` when it detects bot protection (403, 429, 503, CAPTCHA + /// pages, Cloudflare challenges, empty bodies on HTML pages, etc.). + /// This is the recommended mode for production use. + Smart, +} + +/// Configuration for spider.cloud integration. +/// +/// Spider Cloud provides anti-bot bypass, proxy rotation, and high-throughput +/// data collection. Sign up at to obtain an API key. +#[cfg(feature = "spider_cloud")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SpiderCloudConfig { + /// API key / secret. Sign up at to get one. + pub api_key: String, + /// Integration mode. + #[cfg_attr(feature = "serde", serde(default))] + pub mode: SpiderCloudMode, + /// API base URL (default: `https://api.spider.cloud`). + #[cfg_attr( + feature = "serde", + serde(default = "SpiderCloudConfig::default_api_url") + )] + pub api_url: String, + /// Proxy URL (default: `https://proxy.spider.cloud`). + #[cfg_attr( + feature = "serde", + serde(default = "SpiderCloudConfig::default_proxy_url") + )] + pub proxy_url: String, + /// Return format for API mode (default: `"raw"` to get original HTML). + #[cfg_attr( + feature = "serde", + serde(default = "SpiderCloudConfig::default_return_format") + )] + pub return_format: String, + /// Extra params forwarded in API mode (e.g. `stealth`, `fingerprint`, `cache`). + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub extra_params: Option>, +} + +#[cfg(feature = "spider_cloud")] +impl Default for SpiderCloudConfig { + fn default() -> Self { + Self { + api_key: String::new(), + mode: SpiderCloudMode::default(), + api_url: Self::default_api_url(), + proxy_url: Self::default_proxy_url(), + return_format: Self::default_return_format(), + extra_params: None, + } + } +} + +#[cfg(feature = "spider_cloud")] +impl SpiderCloudConfig { + /// Create a new config with defaults (Proxy mode). + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + ..Default::default() + } + } + + /// Set the integration mode. + pub fn with_mode(mut self, mode: SpiderCloudMode) -> Self { + self.mode = mode; + self + } + + /// Set a custom API base URL. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = url.into(); + self + } + + /// Set a custom proxy URL. + pub fn with_proxy_url(mut self, url: impl Into) -> Self { + self.proxy_url = url.into(); + self + } + + /// Set the return format for API mode. + pub fn with_return_format(mut self, fmt: impl Into) -> Self { + self.return_format = fmt.into(); + self + } + + /// Set extra params for API mode. + pub fn with_extra_params( + mut self, + params: hashbrown::HashMap, + ) -> Self { + self.extra_params = Some(params); + self + } + + /// Determine if a response should trigger a spider.cloud API fallback. + /// + /// This encapsulates the intelligence about which status codes and + /// content patterns indicate the page needs spider.cloud's help. + /// + /// Checks for: + /// - HTTP 403 (Forbidden) — typically bot protection + /// - HTTP 429 (Too Many Requests) — rate limiting + /// - HTTP 503 (Service Unavailable) — often Cloudflare/DDoS protection + /// - HTTP 520-530 (Cloudflare error range) + /// - HTTP 5xx (server errors) + /// - Empty body on what should be an HTML page + /// - Known CAPTCHA / challenge page markers in the response body + pub fn should_fallback(&self, status_code: u16, body: Option<&[u8]>) -> bool { + match self.mode { + SpiderCloudMode::Api | SpiderCloudMode::Unblocker => false, // already using API + SpiderCloudMode::Proxy => false, // proxy-only, no fallback + SpiderCloudMode::Fallback | SpiderCloudMode::Smart => { + // Status code triggers + if matches!(status_code, 403 | 429 | 503 | 520..=530) { + return true; + } + if status_code >= 500 { + return true; + } + + // Content-based triggers (Smart mode only) + if self.mode == SpiderCloudMode::Smart { + if let Some(body) = body { + // Empty body when we expected HTML + if body.is_empty() { + return true; + } + + // Check for bot protection / CAPTCHA markers in the body + // (only check first 4KB for performance) + let check_len = body.len().min(4096); + let snippet = String::from_utf8_lossy(&body[..check_len]); + let lower = snippet.to_lowercase(); + + // Cloudflare challenge + if lower.contains("cf-browser-verification") + || lower.contains("cloudflare") && lower.contains("challenge-platform") + { + return true; + } + + // Generic CAPTCHA / bot detection markers + if lower.contains("captcha") && lower.contains("challenge") + || lower.contains("please verify you are a human") + || lower.contains("access denied") && lower.contains("automated") + || lower.contains("bot detection") + { + return true; + } + + // Distil Networks / Imperva / Akamai patterns + if lower.contains("distil_r_captcha") + || lower.contains("_imperva") + || lower.contains("akamai") && lower.contains("bot manager") + { + return true; + } + } + } + + false + } + } + } + + /// Get the fallback API route for this config. + /// + /// - `Smart` mode → `/unblocker` (best for bot-protected pages) + /// - `Fallback` mode → `/crawl` (general purpose) + /// - Other modes → `/crawl` (default) + pub fn fallback_route(&self) -> &'static str { + match self.mode { + SpiderCloudMode::Smart | SpiderCloudMode::Unblocker => "unblocker", + _ => "crawl", + } + } + + /// Whether this mode uses the proxy transport layer. + pub fn uses_proxy(&self) -> bool { + matches!( + self.mode, + SpiderCloudMode::Proxy | SpiderCloudMode::Fallback | SpiderCloudMode::Smart + ) + } + + fn default_api_url() -> String { + "https://api.spider.cloud".to_string() + } + + fn default_proxy_url() -> String { + "https://proxy.spider.cloud".to_string() + } + + fn default_return_format() -> String { + "raw".to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_configuration_defaults() { + let config = Configuration::default(); + assert!(!config.respect_robots_txt); + assert!(!config.subdomains); + assert!(!config.tld); + assert_eq!(config.delay, 0); + assert!(config.user_agent.is_none()); + assert!(config.blacklist_url.is_none()); + assert!(config.whitelist_url.is_none()); + assert!(config.proxies.is_none()); + assert!(!config.http2_prior_knowledge); + } + + #[test] + fn test_redirect_policy_variants() { + assert_eq!(RedirectPolicy::default(), RedirectPolicy::Loose); + let strict = RedirectPolicy::Strict; + let none = RedirectPolicy::None; + assert_ne!(strict, RedirectPolicy::Loose); + assert_ne!(none, RedirectPolicy::Loose); + assert_ne!(strict, none); + } + + #[test] + fn test_proxy_ignore_variants() { + assert_eq!(ProxyIgnore::default(), ProxyIgnore::No); + let chrome = ProxyIgnore::Chrome; + let http = ProxyIgnore::Http; + assert_ne!(chrome, ProxyIgnore::No); + assert_ne!(http, ProxyIgnore::No); + assert_ne!(chrome, http); + } + + #[test] + fn test_request_proxy_construction() { + let proxy = RequestProxy { + addr: "http://proxy.example.com:8080".to_string(), + ignore: ProxyIgnore::No, + }; + assert_eq!(proxy.addr, "http://proxy.example.com:8080"); + assert_eq!(proxy.ignore, ProxyIgnore::No); + } + + #[test] + fn test_request_proxy_default() { + let proxy = RequestProxy::default(); + assert!(proxy.addr.is_empty()); + assert_eq!(proxy.ignore, ProxyIgnore::No); + } + + #[test] + fn test_configuration_blacklist_setup() { + let mut config = Configuration::default(); + config.blacklist_url = Some(vec![ + "https://example.com/private".into(), + "https://example.com/admin".into(), + ]); + assert_eq!(config.blacklist_url.as_ref().unwrap().len(), 2); + } + + #[test] + fn test_configuration_whitelist_setup() { + let mut config = Configuration::default(); + config.whitelist_url = Some(vec!["https://example.com/public".into()]); + assert_eq!(config.whitelist_url.as_ref().unwrap().len(), 1); + } + + #[test] + fn test_configuration_external_domains() { + let mut config = Configuration::default(); + config.external_domains_caseless = Arc::new( + [ + case_insensitive_string::CaseInsensitiveString::from("Example.Com"), + case_insensitive_string::CaseInsensitiveString::from("OTHER.org"), + ] + .into_iter() + .collect(), + ); + assert_eq!(config.external_domains_caseless.len(), 2); + assert!(config.external_domains_caseless.contains( + &case_insensitive_string::CaseInsensitiveString::from("example.com") + )); + } + + #[test] + fn test_configuration_budget() { + let mut config = Configuration::default(); + let mut budget = hashbrown::HashMap::new(); + budget.insert( + case_insensitive_string::CaseInsensitiveString::from("/path"), + 100u32, + ); + config.budget = Some(budget); + assert!(config.budget.is_some()); + assert_eq!( + config.budget.as_ref().unwrap().get( + &case_insensitive_string::CaseInsensitiveString::from("/path") + ), + Some(&100u32) + ); + } + + #[cfg(not(feature = "regex"))] + #[test] + fn test_allow_list_set_default() { + let allow_list = AllowListSet::default(); + assert!(allow_list.0.is_empty()); + } + + #[cfg(feature = "agent")] + #[test] + fn test_build_remote_multimodal_engine_preserves_dual_models() { + use crate::features::automation::{ + ModelEndpoint, RemoteMultimodalConfigs, VisionRouteMode, + }; + + let mut config = Configuration::default(); + let mm = RemoteMultimodalConfigs::new( + "https://api.example.com/v1/chat/completions", + "primary-model", + ) + .with_vision_model(ModelEndpoint::new("vision-model").with_api_key("vision-key")) + .with_text_model( + ModelEndpoint::new("text-model") + .with_api_url("https://text.example.com/v1/chat/completions") + .with_api_key("text-key"), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + config.remote_multimodal = Some(Box::new(mm)); + + let engine = config + .build_remote_multimodal_engine() + .expect("engine should be built"); + + assert_eq!( + engine.vision_model.as_ref().map(|m| m.model_name.as_str()), + Some("vision-model") + ); + assert_eq!( + engine.text_model.as_ref().map(|m| m.model_name.as_str()), + Some("text-model") + ); + assert_eq!(engine.vision_route_mode, VisionRouteMode::TextFirst); + } +} diff --git a/spider/src/features/automation.rs b/spider/src/features/automation.rs new file mode 100644 index 000000000..1ecc4f89c --- /dev/null +++ b/spider/src/features/automation.rs @@ -0,0 +1,1167 @@ +//! Automation module for spider. +//! +//! This module provides web automation capabilities through spider_agent. +//! All core automation types, the RemoteMultimodalEngine, and browser methods +//! are re-exported from spider_agent. +//! +//! Spider-specific types like `PromptConfiguration` (for configuring crawlers +//! from natural language) are defined here. +//! +//! # Feature Requirements +//! +//! - `agent` - Required for automation types and engine +//! - `agent_chrome` - Required for browser automation methods (run, act, observe) +//! +//! # Example +//! +//! ```rust,ignore +//! use spider::features::automation::{RemoteMultimodalEngine, RemoteMultimodalConfigs}; +//! +//! // Create engine for extraction +//! let engine = RemoteMultimodalEngine::new( +//! "https://api.openai.com/v1/chat/completions", +//! "gpt-4o", +//! None, +//! ).with_api_key(Some("sk-...")); +//! +//! // Extract from HTML +//! let result = engine.extract_from_html(html, url, title).await?; +//! ``` + +// ============================================================================= +// Re-exports from spider_agent +// ============================================================================= + +#[cfg(feature = "agent")] +pub use spider_agent::automation::{ + // Helper functions + best_effort_parse_json_object, + // Map result types + categories, + // HTML cleaning + clean_html, + clean_html_base, + clean_html_full, + clean_html_raw, + clean_html_slim, + clean_html_with_profile, + clean_html_with_profile_and_intent, + extract_assistant_content, + extract_last_code_block, + extract_last_json_array, + extract_last_json_boundaries, + extract_last_json_object, + extract_usage, + fnv1a64, + // Config helpers + is_url_allowed, + merged_config, + // Selector cache (already exported above - SelectorCache, SelectorCacheEntry) + smart_clean_html, + truncate_utf8_tail, + ActResult, + // Core types + ActionRecord, + ActionResult, + ActionType, + AutomationConfig, + // Memory operations + AutomationMemory, + AutomationResult, + AutomationUsage, + CaptureProfile, + // Chain types + ChainBuilder, + ChainCondition, + ChainContext, + ChainResult, + ChainStep, + ChainStepResult, + CleaningIntent, + ClipViewport, + ContentAnalysis, + CostTier, + DiscoveredUrl, + // Error types + EngineError, + EngineResult, + ExtractionSchema, + // Observation types + FormField, + FormInfo, + HtmlCleaningProfile, + InteractiveElement, + MapResult, + MemoryOperation, + // Engine and config + ModelEndpoint, + ModelPolicy, + NavigationOption, + PageObservation, + PromptUrlGate, + RecoveryStrategy, + RemoteMultimodalConfig, + RemoteMultimodalConfigs, + RemoteMultimodalEngine, + RetryPolicy, + // Selector cache + SelectorCache, + SelectorCacheEntry, + StructuredOutputConfig, + VisionRouteMode, + // System prompts + ACT_SYSTEM_PROMPT, + CHROME_AI_SYSTEM_PROMPT, + CONFIGURATION_SYSTEM_PROMPT, + DEFAULT_SYSTEM_PROMPT, + EXTRACTION_ONLY_SYSTEM_PROMPT, + EXTRACT_SYSTEM_PROMPT, + MAP_SYSTEM_PROMPT, + OBSERVE_SYSTEM_PROMPT, +}; + +// Skills module for dynamic context injection +#[cfg(feature = "agent_skills")] +pub use spider_agent::automation::skills; + +// Performance types +#[cfg(feature = "agent")] +pub use spider_agent::automation::cache::{CacheStats, CacheValue, SmartCache}; +#[cfg(feature = "agent")] +pub use spider_agent::automation::executor::{BatchExecutor, ChainExecutor, PrefetchManager}; +#[cfg(feature = "agent")] +pub use spider_agent::automation::router::{ + auto_policy, ModelRouter, RoutingDecision, TaskAnalysis, TaskCategory, +}; + +// Browser-specific exports (requires agent + chrome) +#[cfg(all(feature = "agent", feature = "agent_chrome"))] +pub use spider_agent::automation::run_remote_multimodal_with_page; + +// Spawn pages support (requires agent + chrome) +#[cfg(all(feature = "agent", feature = "agent_chrome"))] +pub use spider_agent::automation::{ + run_spawn_pages_concurrent, run_spawn_pages_with_factory, run_spawn_pages_with_options, + PageFactory, PageSetupFn, SpawnPageOptions, SpawnedPageResult, +}; + +// Chrome page type for browser automation (needed for both agent and stub implementations) +#[cfg(feature = "chrome")] +use chromiumoxide::Page; + +// ============================================================================= +// Types from spider_agent_types (always available, no heavy deps) +// ============================================================================= + +/// Token usage tracking — re-exported from [`spider_agent_types`] when agent +/// feature is not enabled. When `agent` is enabled, the type comes from +/// `spider_agent::automation` instead. +#[cfg(not(feature = "agent"))] +pub use spider_agent_types::{AutomationResult, AutomationUsage}; + +/// Engine error type (stub when agent feature not enabled). +#[cfg(not(feature = "agent"))] +#[derive(Debug)] +pub enum EngineError { + /// HTTP error. + Http(reqwest::Error), + /// JSON error. + #[cfg(feature = "serde")] + Json(serde_json::Error), + /// Missing field. + MissingField(&'static str), + /// Invalid field. + InvalidField(&'static str), + /// Remote error. + Remote(String), + /// Unsupported operation. + Unsupported(&'static str), +} + +#[cfg(not(feature = "agent"))] +impl std::fmt::Display for EngineError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EngineError::Http(e) => write!(f, "http error: {e}"), + #[cfg(feature = "serde")] + EngineError::Json(e) => write!(f, "json error: {e}"), + EngineError::MissingField(s) => write!(f, "missing field: {s}"), + EngineError::InvalidField(s) => write!(f, "invalid field: {s}"), + EngineError::Remote(s) => write!(f, "remote error: {s}"), + EngineError::Unsupported(s) => write!(f, "unsupported: {s}"), + } + } +} + +#[cfg(not(feature = "agent"))] +impl std::error::Error for EngineError {} + +#[cfg(not(feature = "agent"))] +impl From for EngineError { + fn from(e: reqwest::Error) -> Self { + EngineError::Http(e) + } +} + +#[cfg(all(not(feature = "agent"), feature = "serde"))] +impl From for EngineError { + fn from(e: serde_json::Error) -> Self { + EngineError::Json(e) + } +} + +/// Convenience result type. +#[cfg(not(feature = "agent"))] +pub type EngineResult = Result; + +/// Remote multimodal configs (stub when agent feature not enabled). +#[cfg(not(feature = "agent"))] +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RemoteMultimodalConfigs { + /// API URL. + pub api_url: String, + /// API key. + pub api_key: Option, + /// Model name. + pub model_name: String, + /// Use Chrome built-in AI (Gemini Nano) instead of a remote API. + #[cfg_attr(feature = "serde", serde(default))] + pub use_chrome_ai: bool, +} + +#[cfg(not(feature = "agent"))] +impl RemoteMultimodalConfigs { + /// Create a new remote multimodal config (stub - does nothing without agent feature). + pub fn new(api_url: impl Into, model_name: impl Into) -> Self { + Self { + api_url: api_url.into(), + model_name: model_name.into(), + ..Default::default() + } + } + + /// Set the API key (stub - does nothing without agent feature). + pub fn with_api_key(mut self, api_key: Option>) -> Self { + self.api_key = api_key.map(|k| k.into()); + self + } + + /// Whether to use Chrome built-in AI (Gemini Nano) for inference. + pub fn should_use_chrome_ai(&self) -> bool { + self.use_chrome_ai || (self.api_url.is_empty() && self.api_key.is_none()) + } +} + +// ============================================================================= +// Spider-specific types and functions +// ============================================================================= + +/// Configuration response from the LLM for prompt-based crawler setup. +/// +/// This type is specific to spider's Website configuration. Use it with +/// `configure_crawler_from_prompt` to generate crawler settings from +/// natural language descriptions. +/// +/// # Example +/// +/// ```rust,ignore +/// use spider::features::automation::configure_crawler_from_prompt; +/// +/// let config = configure_crawler_from_prompt( +/// "http://localhost:11434/v1/chat/completions", +/// "llama3", +/// None, +/// "Crawl only blog posts, max 50 pages, respect robots.txt" +/// ).await?; +/// +/// // Apply to website +/// website.apply_prompt_configuration(&config); +/// ``` +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct PromptConfiguration { + /// Respect robots.txt rules. + pub respect_robots_txt: Option, + /// Crawl subdomains. + pub subdomains: Option, + /// Crawl top-level domain variants. + pub tld: Option, + /// Maximum crawl depth. + pub depth: Option, + /// Delay between requests in milliseconds. + pub delay: Option, + /// Request timeout in milliseconds. + pub request_timeout_ms: Option, + /// Total crawl timeout in milliseconds. + pub crawl_timeout_ms: Option, + /// URL patterns to exclude. + pub blacklist_url: Option>, + /// URL patterns to include exclusively. + pub whitelist_url: Option>, + /// External domains to allow crawling. + pub external_domains: Option>, + /// User agent string. + pub user_agent: Option, + /// Custom HTTP headers. + pub headers: Option>, + /// Use HTTP/2 prior knowledge. + pub http2_prior_knowledge: Option, + /// Accept invalid SSL certificates. + pub accept_invalid_certs: Option, + /// Proxy URLs for requests. + pub proxies: Option>, + /// Maximum redirect limit. + pub redirect_limit: Option, + /// Budget limits per path or domain. + pub budget: Option>, + /// Maximum bytes per page. + pub max_page_bytes: Option, + /// Crawl all resources including assets. + pub full_resources: Option, + /// Only crawl HTML pages. + pub only_html: Option, + /// Return discovered links with pages. + pub return_page_links: Option, + /// Use headless Chrome for rendering. + pub use_chrome: Option, + /// Stealth mode level: "none", "basic", "low", "mid", "full". + pub stealth_mode: Option, + /// Browser viewport width. + pub viewport_width: Option, + /// Browser viewport height. + pub viewport_height: Option, + /// Wait for network to be idle. + pub wait_for_idle_network: Option, + /// Delay after page load in milliseconds. + pub wait_for_delay_ms: Option, + /// CSS selector to wait for. + pub wait_for_selector: Option, + /// JavaScript to inject on each page. + pub evaluate_on_new_document: Option, + /// Use shared queue for even distribution. + pub shared_queue: Option, + /// Retry attempts for failed requests. + pub retry: Option, +} + +/// Generate crawler configuration from a natural language prompt. +/// +/// This function creates a RemoteMultimodalEngine and uses it to generate +/// a PromptConfiguration from the given prompt. The configuration can then +/// be applied to a Website using `apply_prompt_configuration`. +/// +/// # Arguments +/// * `api_url` - OpenAI-compatible chat completions endpoint +/// * `model_name` - Model identifier (e.g., "gpt-4", "llama3", "qwen2.5") +/// * `api_key` - Optional API key for authenticated endpoints +/// * `prompt` - Natural language description of crawling requirements +/// +/// # Example +/// +/// ```rust,ignore +/// let config = configure_crawler_from_prompt( +/// "http://localhost:11434/v1/chat/completions", +/// "llama3", +/// None, +/// "Crawl only blog posts, max 50 pages, respect robots.txt" +/// ).await?; +/// +/// website.apply_prompt_configuration(&config); +/// ``` +#[cfg(all(feature = "agent", feature = "serde"))] +pub async fn configure_crawler_from_prompt( + api_url: &str, + model_name: &str, + api_key: Option<&str>, + prompt: &str, +) -> EngineResult { + use serde::Serialize; + + #[derive(Serialize)] + struct Message { + role: String, + content: String, + } + + #[derive(Serialize)] + struct ResponseFormat { + #[serde(rename = "type")] + format_type: String, + } + + #[derive(Serialize)] + struct InferenceRequest { + model: String, + messages: Vec, + temperature: f32, + max_tokens: u16, + #[serde(skip_serializing_if = "Option::is_none")] + response_format: Option, + } + + static CLIENT: std::sync::LazyLock = + std::sync::LazyLock::new(reqwest::Client::new); + + let request_body = InferenceRequest { + model: model_name.to_string(), + messages: vec![ + Message { + role: "system".into(), + content: CONFIGURATION_SYSTEM_PROMPT.to_string(), + }, + Message { + role: "user".into(), + content: format!( + "Configure a web crawler for the following requirements:\n\n{}", + prompt + ), + }, + ], + temperature: 0.1, + max_tokens: 2048, + response_format: Some(ResponseFormat { + format_type: "json_object".into(), + }), + }; + + let mut req = CLIENT.post(api_url).json(&request_body); + if let Some(key) = api_key { + req = req.bearer_auth(key); + } + + let http_resp = req.send().await.map_err(EngineError::Http)?; + let status = http_resp.status(); + let raw_body = http_resp.text().await.map_err(EngineError::Http)?; + + if !status.is_success() { + return Err(EngineError::Remote(format!( + "non-success status {status}: {raw_body}" + ))); + } + + let root: serde_json::Value = serde_json::from_str(&raw_body) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))?; + + let content = extract_assistant_content(&root) + .ok_or(EngineError::MissingField("choices[0].message.content"))?; + + let config_value = best_effort_parse_json_object(&content)?; + + let config: PromptConfiguration = serde_json::from_value(config_value) + .map_err(|e| EngineError::Remote(format!("Failed to parse configuration: {e}")))?; + + Ok(config) +} + +/// Run remote multi-modal automation if enabled in the configuration. +/// +/// This is a convenience function that checks if automation is configured +/// and runs it on the given browser page. +/// +/// # Arguments +/// * `cfgs` - Optional automation configuration +/// * `page` - Chrome browser page +/// * `url` - URL being processed +/// +/// # Returns +/// * `Ok(None)` - Automation not configured +/// * `Ok(Some(result))` - Automation result +/// * `Err(e)` - Automation failed +#[cfg(all(feature = "agent", feature = "agent_chrome"))] +pub async fn run_remote_multimodal_if_enabled( + cfgs: &Option>, + page: &Page, + url: &str, +) -> EngineResult> { + let cfgs = match cfgs.as_deref() { + Some(c) => c, + None => return Ok(None), + }; + + let result = run_remote_multimodal_with_page(cfgs, page, url).await?; + + // Increment relevance credits for irrelevant pages + if result.relevant == Some(false) { + cfgs.relevance_credits + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + + Ok(Some(result)) +} + +/// Run remote multi-modal extraction on raw HTML content (no browser required). +/// +/// This function enables extraction from HTTP responses without requiring Chrome. +/// It sends the HTML content to the multimodal model for structured data extraction. +/// +/// Note: This only supports extraction (`extra_ai_data`), not browser automation. +#[cfg(all(feature = "agent", feature = "serde"))] +pub async fn run_remote_multimodal_extraction( + cfgs: &Option>, + html: &str, + url: &str, + title: Option<&str>, +) -> EngineResult> { + let cfgs = match cfgs.as_deref() { + Some(c) => c, + None => return Ok(None), + }; + + // Only run if extraction is enabled + if !cfgs.cfg.extra_ai_data { + return Ok(None); + } + + // Check URL gating + if let Some(gate) = &cfgs.prompt_url_gate { + if !gate.is_allowed(url) { + return Ok(Some(AutomationResult { + label: "url_not_allowed".into(), + steps_executed: 0, + success: true, + error: None, + usage: AutomationUsage::default(), + extracted: None, + screenshot: None, + spawn_pages: Vec::new(), + relevant: None, + reasoning: None, + })); + } + } + + let sem = cfgs.get_or_init_semaphore(); + let mut engine = RemoteMultimodalEngine::new( + cfgs.api_url.clone(), + cfgs.model_name.clone(), + cfgs.system_prompt.clone(), + ) + .with_api_key(cfgs.api_key.as_deref()); + + engine.with_system_prompt_extra(cfgs.system_prompt_extra.as_deref()); + engine.with_user_message_extra(cfgs.user_message_extra.as_deref()); + engine.with_remote_multimodal_config(cfgs.cfg.clone()); + engine.with_prompt_url_gate(cfgs.prompt_url_gate.clone()); + engine.with_semaphore(sem); + + let result = engine.extract_from_html(html, url, title).await?; + + // Increment relevance credits for irrelevant pages + if result.relevant == Some(false) { + cfgs.relevance_credits + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + + Ok(Some(result)) +} + +// ============================================================================= +// URL pre-filter +// ============================================================================= + +/// Pre-filter URLs using LLM classification. Returns only URLs classified as relevant. +/// +/// Both `url_prefilter` and `relevance_gate` must be enabled on the config. +/// On any error, all input URLs are returned (safe fallback). +#[cfg(all(feature = "agent", feature = "serde"))] +pub(crate) async fn prefilter_urls( + cfgs: &RemoteMultimodalConfigs, + urls: &hashbrown::HashSet, +) -> hashbrown::HashSet { + use case_insensitive_string::CaseInsensitiveString; + + if !cfgs.cfg.url_prefilter || !cfgs.cfg.relevance_gate || urls.is_empty() { + return urls.clone(); + } + + let batch_size = cfgs.cfg.url_prefilter_batch_size; + let max_tokens = cfgs.cfg.url_prefilter_max_tokens; + + // Partition URLs into cached and uncached + let mut relevant_set: hashbrown::HashSet = + hashbrown::HashSet::with_capacity(urls.len()); + let mut uncached: Vec = Vec::new(); + + for url in urls { + let path = url_to_cache_key(url.inner().as_str()); + match cfgs.url_prefilter_cache.get(&path).map(|v| *v.value()) { + Some(true) => { + relevant_set.insert(url.clone()); + } + Some(false) => { + // cached as irrelevant — skip + } + None => { + uncached.push(url.clone()); + } + } + } + + if uncached.is_empty() { + return relevant_set; + } + + // Build engine from cfgs + let sem = cfgs.get_or_init_semaphore(); + let mut engine = RemoteMultimodalEngine::new( + cfgs.api_url.clone(), + cfgs.model_name.clone(), + cfgs.system_prompt.clone(), + ) + .with_api_key(cfgs.api_key.as_deref()); + + engine.with_semaphore(sem); + + // Copy dual-model routing so text model is used + engine.with_vision_model(cfgs.vision_model.clone()); + engine.with_text_model(cfgs.text_model.clone()); + engine.with_vision_route_mode(cfgs.vision_route_mode); + + // Process in batches + for batch in uncached.chunks(batch_size) { + let url_strs: Vec<&str> = batch.iter().map(|u| u.inner().as_str()).collect(); + + let classifications = match engine + .classify_urls( + &url_strs, + cfgs.cfg.relevance_prompt.as_deref(), + cfgs.cfg.extraction_prompt.as_deref(), + max_tokens, + ) + .await + { + Ok(c) => c, + Err(e) => { + log::warn!("url_prefilter: classify_urls error, assuming all relevant: {e}"); + // On error, include all from this batch + relevant_set.extend(batch.iter().cloned()); + continue; + } + }; + + // Update cache and build relevant set + for (url, &is_relevant) in batch.iter().zip(classifications.iter()) { + let path = url_to_cache_key(url.inner().as_str()); + cfgs.url_prefilter_cache.insert(path, is_relevant); + if is_relevant { + relevant_set.insert(url.clone()); + } + } + } + + relevant_set +} + +/// Extract a cache key from a URL including origin + path + query. +/// +/// This avoids collisions across domains that share the same path. +#[cfg(all(feature = "agent", feature = "serde"))] +fn url_to_cache_key(url: &str) -> String { + if let Ok(parsed) = url::Url::parse(url) { + let mut key = String::new(); + key.push_str(parsed.scheme()); + key.push_str("://"); + key.push_str(parsed.host_str().unwrap_or_default()); + if let Some(port) = parsed.port() { + key.push(':'); + key.push_str(&port.to_string()); + } + key.push_str(parsed.path()); + if let Some(query) = parsed.query() { + key.push('?'); + key.push_str(query); + } + return key; + } + url.to_string() +} + +// ============================================================================= +// Conversion helpers +// ============================================================================= + +/// Extension trait for converting AutomationResult to spider's AutomationResults. +#[cfg(feature = "agent")] +pub trait AutomationResultExt { + /// Convert to spider's AutomationResults format. + fn to_automation_results(&self) -> crate::page::AutomationResults; +} + +#[cfg(all(feature = "agent", feature = "serde"))] +impl AutomationResultExt for AutomationResult { + fn to_automation_results(&self) -> crate::page::AutomationResults { + crate::page::AutomationResults { + input: self.label.clone(), + content_output: self.extracted.clone().unwrap_or(serde_json::Value::Null), + screenshot_output: self.screenshot.clone(), + error: self.error.clone(), + usage: Some(self.usage.clone()), + relevant: self.relevant, + steps_executed: (self.steps_executed > 0).then_some(self.steps_executed), + reasoning: self + .reasoning + .as_ref() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()), + } + } +} + +#[cfg(all(feature = "agent", not(feature = "serde")))] +impl AutomationResultExt for AutomationResult { + fn to_automation_results(&self) -> crate::page::AutomationResults { + crate::page::AutomationResults { + input: self.label.clone(), + content_output: String::new(), + screenshot_output: self.screenshot.clone(), + error: self.error.clone(), + usage: Some(self.usage.clone()), + relevant: self.relevant, + steps_executed: (self.steps_executed > 0).then_some(self.steps_executed), + reasoning: self + .reasoning + .as_ref() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()), + } + } +} + +// ============================================================================= +// Spawn pages helper for spider +// ============================================================================= + +/// Process spawn_pages URLs concurrently with full spider configuration. +/// +/// This function creates new pages using the browser, applies spider's full +/// configuration (stealth mode, fingerprinting, event tracking, etc.), and runs +/// automation on each page concurrently. +/// +/// # Arguments +/// * `browser` - The browser to create new pages from +/// * `browser_context_id` - Optional browser context ID for incognito mode +/// * `urls` - URLs to open (typically from `page_response.spawn_pages`) +/// * `mm_cfgs` - Configuration for the automation engine +/// * `spider_config` - Spider configuration to apply to each spawned page +/// * `options` - Additional spawn options (extraction, screenshots, etc.) +/// +/// # Example +/// ```ignore +/// if let Some(spawn_urls) = page_response.spawn_pages.take() { +/// let options = SpawnPageOptions::new() +/// .with_extraction("Extract page content"); +/// +/// let results = process_spawn_pages_with_config( +/// &browser, +/// &browser_context_id, +/// spawn_urls, +/// &mm_config, +/// &configuration, +/// options, +/// ).await; +/// } +/// ``` +#[cfg(all(feature = "agent", feature = "agent_chrome"))] +pub async fn process_spawn_pages_with_config( + browser: &std::sync::Arc, + browser_context_id: &Option, + urls: Vec, + mm_cfgs: &RemoteMultimodalConfigs, + spider_config: &crate::configuration::Configuration, + options: SpawnPageOptions, +) -> Vec { + use std::sync::Arc; + + if urls.is_empty() { + return Vec::new(); + } + + let mm_cfgs = Arc::new(mm_cfgs.clone()); + let spider_config = Arc::new(spider_config.clone()); + let options = Arc::new(options); + let browser_context_id = browser_context_id.clone(); + + let mut handles = Vec::with_capacity(urls.len()); + + // Check if we should track network events + let track_events = spider_config + .track_events + .as_ref() + .map(|t| t.responses) + .unwrap_or(false); + + for url in urls { + let browser = browser.clone(); + let mm_cfgs = mm_cfgs.clone(); + let spider_config = spider_config.clone(); + let options = options.clone(); + let browser_context_id = browser_context_id.clone(); + let url_clone = url.clone(); + + handles.push(tokio::spawn(async move { + use chromiumoxide::cdp::browser_protocol::network::EventDataReceived; + use tokio_stream::StreamExt; + + // Create new page using attempt_navigation (spider's standard approach) + let new_page = match crate::features::chrome::attempt_navigation( + &url_clone, + &browser, + &spider_config.request_timeout, + &browser_context_id, + &spider_config.viewport, + ) + .await + { + Ok(page) => page, + Err(e) => { + return SpawnedPageResult { + url: url_clone, + result: Err(format!("Failed to create page: {}", e)), + bytes_transferred: None, + response_map: None, + }; + } + }; + + // Apply spider configuration (stealth, fingerprinting, event tracking) + crate::features::chrome::setup_chrome_events(&new_page, &spider_config).await; + + // Set up bytes tracking listener if event tracking is enabled + let bytes_listener = if track_events { + new_page.event_listener::().await.ok() + } else { + None + }; + + // Track bytes in a separate task while automation runs + use std::sync::atomic::{AtomicU64, Ordering}; + let total_bytes: std::sync::Arc = std::sync::Arc::new(AtomicU64::new(0)); + let response_map: std::sync::Arc> = + std::sync::Arc::new(dashmap::DashMap::new()); + + let total_bytes_clone = total_bytes.clone(); + let response_map_clone = response_map.clone(); + let listener_handle = bytes_listener.map(|mut listener| { + tokio::spawn(async move { + while let Some(event) = listener.next().await { + let bytes = event.encoded_data_length as u64; + total_bytes_clone.fetch_add(bytes, Ordering::Relaxed); + response_map_clone + .entry(event.request_id.inner().clone()) + .and_modify(|v| *v += bytes) + .or_insert(bytes); + } + }) + }); + + // Build page-specific config from the full base config to preserve + // routing, schemas, relevance gates, and other production knobs. + let mut page_cfgs = (*mm_cfgs).clone(); + let mut page_cfg = page_cfgs.cfg.clone(); + page_cfg.max_rounds = options.max_rounds.max(1); + page_cfg.screenshot = options.screenshot; + + // Enable extraction if prompt is provided + if let Some(ref prompt) = options.extraction_prompt { + page_cfg.extra_ai_data = true; + page_cfg.extraction_prompt = Some(prompt.clone()); + } + + page_cfgs.cfg = page_cfg; + + // Add/override user message extra if provided + if let Some(ref msg) = options.user_message_extra { + page_cfgs.user_message_extra = Some(msg.clone()); + } + + // Run automation on the new page + let result = run_remote_multimodal_with_page(&page_cfgs, &new_page, &url_clone) + .await + .map_err(|e| format!("Automation failed: {}", e)); + + // Stop the listener and collect bytes data + if let Some(handle) = listener_handle { + handle.abort(); + } + + let (bytes_transferred, response_map_out) = { + let bytes_val = total_bytes.load(Ordering::Relaxed); + let bytes = if bytes_val > 0 { + Some(bytes_val as f64) + } else { + None + }; + let map = if !response_map.is_empty() { + Some( + response_map + .iter() + .map(|e| (e.key().clone(), *e.value() as f64)) + .collect(), + ) + } else { + None + }; + (bytes, map) + }; + + SpawnedPageResult { + url: url_clone, + result, + bytes_transferred, + response_map: response_map_out, + } + })); + } + + // Collect all results + let mut results = Vec::with_capacity(handles.len()); + for handle in handles { + match handle.await { + Ok(spawn_result) => results.push(spawn_result), + Err(e) => { + results.push(SpawnedPageResult { + url: "unknown".to_string(), + result: Err(format!("Task failed: {}", e)), + bytes_transferred: None, + response_map: None, + }); + } + } + } + + results +} + +// ============================================================================= +// Stub implementations (for various feature combinations) +// ============================================================================= + +/// Stub implementation when agent_chrome feature is not enabled. +/// Returns Ok(None) to indicate automation is not configured. +#[cfg(all(feature = "chrome", not(feature = "agent_chrome")))] +pub async fn run_remote_multimodal_if_enabled( + _cfgs: &Option>, + _page: &Page, + _url: &str, +) -> EngineResult> { + Ok(None) +} + +/// Extension trait for converting AutomationResult (stub when agent not enabled). +#[cfg(all(feature = "chrome", not(feature = "agent")))] +pub trait AutomationResultExt { + /// Convert to spider's AutomationResults format. + fn to_automation_results(&self) -> crate::page::AutomationResults; +} + +#[cfg(all(feature = "chrome", not(feature = "agent")))] +impl AutomationResultExt for AutomationResult { + fn to_automation_results(&self) -> crate::page::AutomationResults { + crate::page::AutomationResults { + input: self.label.clone(), + content_output: Default::default(), + screenshot_output: self.screenshot.clone(), + error: self.error.clone(), + usage: Some(self.usage.clone()), + relevant: None, + steps_executed: (self.steps_executed > 0).then_some(self.steps_executed), + reasoning: self + .reasoning + .as_ref() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use case_insensitive_string::CaseInsensitiveString; + + #[test] + fn test_prompt_configuration_default() { + let config = PromptConfiguration::default(); + assert!(config.respect_robots_txt.is_none()); + assert!(config.depth.is_none()); + } + + #[cfg(feature = "serde")] + #[test] + fn test_prompt_configuration_serde() { + let json = r#"{"respect_robots_txt": true, "depth": 5}"#; + let config: PromptConfiguration = serde_json::from_str(json).unwrap(); + assert_eq!(config.respect_robots_txt, Some(true)); + assert_eq!(config.depth, Some(5)); + } + + #[cfg(feature = "agent")] + #[test] + fn test_automation_result_spawn_pages() { + // Test that spawn_pages can be set and checked + let result = AutomationResult::success("test", 1).with_spawn_pages(vec![ + "https://example.com/page1".to_string(), + "https://example.com/page2".to_string(), + ]); + + assert!(result.has_spawn_pages()); + assert_eq!(result.spawn_pages.len(), 2); + assert_eq!(result.spawn_pages[0], "https://example.com/page1"); + assert_eq!(result.spawn_pages[1], "https://example.com/page2"); + + // Test empty spawn_pages + let result = AutomationResult::success("test", 1); + assert!(!result.has_spawn_pages()); + assert!(result.spawn_pages.is_empty()); + } + + #[cfg(all(feature = "agent", feature = "agent_chrome"))] + #[test] + fn test_spawn_page_options() { + let options = SpawnPageOptions::new() + .with_extraction("Extract content") + .with_screenshot(true) + .with_max_rounds(2); + + assert_eq!( + options.extraction_prompt, + Some("Extract content".to_string()) + ); + assert!(options.screenshot); + assert_eq!(options.max_rounds, 2); + } + + #[cfg(all(feature = "agent", feature = "agent_chrome"))] + #[test] + fn test_spawned_page_result_accessors() { + // Test success case + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Ok(AutomationResult::success("test", 1) + .with_extracted(serde_json::json!({"key": "value"}))), + bytes_transferred: Some(2048.0), + response_map: None, + }; + + assert!(result.is_ok()); + assert!(result.error().is_none()); + assert_eq!(result.label(), Some("test")); + assert!(result.extracted().is_some()); + assert_eq!(result.bytes_transferred, Some(2048.0)); + + // Test error case + let error_result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Err("Connection error".to_string()), + bytes_transferred: None, + response_map: None, + }; + + assert!(!error_result.is_ok()); + assert_eq!(error_result.error(), Some("Connection error")); + assert!(error_result.label().is_none()); + } + + #[cfg(all(feature = "agent", feature = "agent_chrome"))] + #[test] + fn test_spawned_page_result_with_response_map() { + let mut response_map = std::collections::HashMap::new(); + response_map.insert("req-123".to_string(), 1024.0); + response_map.insert("req-456".to_string(), 2048.0); + + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Ok(AutomationResult::success("test", 1)), + bytes_transferred: Some(3072.0), + response_map: Some(response_map), + }; + + assert!(result.bytes_transferred.is_some()); + assert_eq!(result.bytes_transferred.unwrap(), 3072.0); + assert!(result.response_map.is_some()); + assert_eq!(result.response_map.as_ref().unwrap().len(), 2); + } + + #[cfg(all(feature = "agent", feature = "serde"))] + #[test] + fn test_url_to_cache_key_includes_origin() { + let a = url_to_cache_key("https://a.example.com/path"); + let b = url_to_cache_key("https://b.example.com/path"); + assert_ne!(a, b); + + let with_query = url_to_cache_key("https://a.example.com/path?x=1&y=2"); + assert_eq!(with_query, "https://a.example.com/path?x=1&y=2"); + } + + #[cfg(all(feature = "agent", feature = "serde"))] + #[tokio::test] + async fn test_prefilter_urls_returns_cached_relevance_without_network() { + use hashbrown::HashSet; + + let mut cfgs = RemoteMultimodalConfigs::new("http://localhost:65535", "test-model"); + cfgs.cfg.relevance_gate = true; + cfgs.cfg.url_prefilter = true; + + let urls: HashSet = [ + "https://a.example.com/p/1", + "https://b.example.com/p/2", + "https://c.example.com/p/3", + ] + .into_iter() + .map(CaseInsensitiveString::from) + .collect(); + + cfgs.url_prefilter_cache + .insert(url_to_cache_key("https://a.example.com/p/1"), true); + cfgs.url_prefilter_cache + .insert(url_to_cache_key("https://b.example.com/p/2"), false); + cfgs.url_prefilter_cache + .insert(url_to_cache_key("https://c.example.com/p/3"), true); + + let filtered = prefilter_urls(&cfgs, &urls).await; + + assert_eq!(filtered.len(), 2); + assert!(filtered.contains(&CaseInsensitiveString::from("https://a.example.com/p/1"))); + assert!(filtered.contains(&CaseInsensitiveString::from("https://c.example.com/p/3"))); + assert!(!filtered.contains(&CaseInsensitiveString::from("https://b.example.com/p/2"))); + } + + #[cfg(all(feature = "agent", feature = "serde"))] + #[tokio::test] + async fn test_prefilter_urls_concurrent_cached_reads_are_stable() { + use hashbrown::HashSet; + use std::sync::Arc; + + let mut cfgs = RemoteMultimodalConfigs::new("http://localhost:65535", "test-model"); + cfgs.cfg.relevance_gate = true; + cfgs.cfg.url_prefilter = true; + cfgs.cfg.url_prefilter_batch_size = 64; + + let urls: HashSet = (0..120usize) + .map(|i| format!("https://example.com/p/{i}")) + .map(CaseInsensitiveString::from) + .collect(); + + for (idx, url) in urls.iter().enumerate() { + cfgs.url_prefilter_cache + .insert(url_to_cache_key(url.inner().as_str()), idx % 2 == 0); + } + + let cfgs = Arc::new(cfgs); + let urls = Arc::new(urls); + + let mut tasks = tokio::task::JoinSet::new(); + for _ in 0..32usize { + let cfgs = cfgs.clone(); + let urls = urls.clone(); + tasks.spawn(async move { prefilter_urls(&cfgs, &urls).await.len() }); + } + + while let Some(res) = tasks.join_next().await { + let len = res.expect("prefilter task should not panic"); + assert_eq!(len, 60); + } + } +} diff --git a/spider/src/features/chrome.rs b/spider/src/features/chrome.rs new file mode 100644 index 000000000..4ef651e05 --- /dev/null +++ b/spider/src/features/chrome.rs @@ -0,0 +1,1134 @@ +use crate::features::chrome_args::CHROME_ARGS; +use crate::utils::{detect_chrome::get_detect_chrome_executable, log}; +use crate::{configuration::Configuration, tokio_stream::StreamExt}; +use chromiumoxide::cdp::browser_protocol::browser::{ + SetDownloadBehaviorBehavior, SetDownloadBehaviorParamsBuilder, +}; +use chromiumoxide::cdp::browser_protocol::{ + browser::BrowserContextId, emulation::SetGeolocationOverrideParams, network::CookieParam, + target::CreateTargetParams, +}; +use chromiumoxide::error::CdpError; +use chromiumoxide::handler::REQUEST_TIMEOUT; +use chromiumoxide::serde_json; +use chromiumoxide::Page; +use chromiumoxide::{handler::HandlerConfig, Browser, BrowserConfig}; +use lazy_static::lazy_static; +#[cfg(feature = "cookies")] +use std::sync::Arc; +use std::time::Duration; +use tokio::task::JoinHandle; +use url::Url; + +lazy_static! { + /// Enable loopback for proxy. + static ref LOOP_BACK_PROXY: bool = std::env::var("LOOP_BACK_PROXY").unwrap_or_default() == "true"; +} + +#[cfg(feature = "cookies")] +/// Parse a cookie into a jar. This does nothing without the 'cookies' flag. +pub fn parse_cookies_with_jar( + jar: &Arc, + cookie_str: &str, + url: &Url, +) -> Result, String> { + use crate::client::cookie::CookieStore; + + // Retrieve cookies stored in the jar + if let Some(header_value) = jar.cookies(url) { + let cookie_header_str = header_value.to_str().map_err(|e| e.to_string())?; + let cookie_pairs: Vec<&str> = cookie_header_str.split(';').collect(); + + let mut cookies = Vec::new(); + + for pair in cookie_pairs { + let parts: Vec<&str> = pair.trim().splitn(2, '=').collect(); + + if parts.len() == 2 { + let name = parts[0].trim(); + let value = parts[1].trim(); + + let mut builder = CookieParam::builder() + .name(name) + .value(value) + .url(url.as_str()); + + if let Some(domain) = url.domain() { + builder = builder.domain(domain.to_string()); + } + + let path = url.path(); + builder = builder.path(if path.is_empty() { "/" } else { path }); + + if cookie_str.contains("Secure") { + builder = builder.secure(true); + } + + if cookie_str.contains("HttpOnly") { + builder = builder.http_only(true); + } + match builder.build() { + Ok(cookie_param) => cookies.push(cookie_param), + Err(e) => return Err(e), + } + } else { + return Err(format!("Invalid cookie pair: {}", pair)); + } + } + + Ok(cookies) + } else { + Err("No cookies found".to_string()) + } +} + +/// Parse a cookie into a jar. This does nothing without the 'cookies' flag. +#[cfg(not(feature = "cookies"))] +pub fn parse_cookies_with_jar(cookie_str: &str, url: &Url) -> Result, String> { + Ok(Default::default()) +} + +#[cfg(feature = "cookies")] +/// Seed jar from cookie header. +pub fn seed_jar_from_cookie_header( + jar: &std::sync::Arc, + cookie_header: &str, + url: &url::Url, +) -> Result<(), String> { + for pair in cookie_header.split(';') { + let pair = pair.trim(); + if pair.is_empty() { + continue; + } + + let (name, value) = pair + .split_once('=') + .ok_or_else(|| format!("Invalid cookie pair: {pair}"))?; + + let set_cookie = format!("{}={}; Path=/", name.trim(), value.trim()); + jar.add_cookie_str(&set_cookie, url); + } + Ok(()) +} + +#[cfg(all(feature = "cookies", feature = "chrome"))] +/// Set the page cookies. +pub async fn set_page_cookies( + page: &chromiumoxide::Page, + cookies: Vec, +) -> Result<(), String> { + use chromiumoxide::cdp::browser_protocol::network::SetCookiesParams; + + if cookies.is_empty() { + return Ok(()); + } + + page.execute(SetCookiesParams::new(cookies)) + .await + .map_err(|e| e.to_string())?; + + Ok(()) +} + +#[cfg(feature = "cookies")] +/// Set cookie params from jar. +pub fn cookie_params_from_jar( + jar: &std::sync::Arc, + url: &url::Url, +) -> Result, String> { + use crate::client::cookie::CookieStore; + use chromiumoxide::cdp::browser_protocol::network::CookieParam; + + let Some(header_value) = jar.cookies(url) else { + return Ok(Vec::new()); + }; + + let s = header_value.to_str().map_err(|e| e.to_string())?; + let mut out = Vec::new(); + + for pair in s.split(';') { + let pair = pair.trim(); + if pair.is_empty() { + continue; + } + + let (name, value) = pair + .split_once('=') + .ok_or_else(|| format!("Invalid cookie pair: {pair}"))?; + + let cp = CookieParam::builder() + .name(name.trim()) + .value(value.trim()) + .url(url.as_str()) + .build() + .map_err(|e| e.to_string())?; + + out.push(cp); + } + + Ok(out) +} + +/// Handle the browser cookie configurations. +#[cfg(feature = "cookies")] +pub async fn set_cookies( + jar: &Arc, + config: &Configuration, + url_parsed: &Option>, + browser: &Browser, +) { + if config.cookie_str.is_empty() { + return; + } + + let Some(parsed) = url_parsed.as_deref() else { + return; + }; + + let _ = seed_jar_from_cookie_header(jar, &config.cookie_str, parsed); + + match parse_cookies_with_jar(jar, &config.cookie_str, parsed) { + Ok(cookies) if !cookies.is_empty() => { + let _ = browser.set_cookies(cookies).await; + } + _ => {} + } +} + +/// Patch Chrome args to enable the built-in AI (LanguageModel / Gemini Nano). +/// +/// Removes `OptimizationHints` from `--disable-features` (which blocks the +/// on-device model), and adds the required `--enable-features` flags. +fn patch_chrome_ai_args(args: &mut Vec) { + for arg in args.iter_mut() { + // Remove OptimizationHints from --disable-features + if arg.starts_with("--disable-features=") { + let features: Vec<&str> = arg["--disable-features=".len()..] + .split(',') + .filter(|f| *f != "OptimizationHints") + .collect(); + *arg = format!("--disable-features={}", features.join(",")); + } + // Append AI features to existing --enable-features + if arg.starts_with("--enable-features=") { + arg.push_str(",OptimizationGuideOnDeviceModel:BypassPerfRequirement/true,PromptAPIForGeminiNano,PromptAPIForGeminiNanoMultimodalInput"); + } + } + // If no --enable-features existed, add one + if !args.iter().any(|a| a.starts_with("--enable-features=")) { + args.push("--enable-features=OptimizationGuideOnDeviceModel:BypassPerfRequirement/true,PromptAPIForGeminiNano,PromptAPIForGeminiNanoMultimodalInput".to_string()); + } +} + +/// get chrome configuration +#[cfg(not(feature = "chrome_headed"))] +pub fn get_browser_config( + proxies: &Option>, + intercept: bool, + cache_enabled: bool, + viewport: impl Into>, + request_timeout: &Option, + use_chrome_ai: bool, +) -> Option { + let builder = BrowserConfig::builder() + .disable_default_args() + .no_sandbox() + .request_timeout(match request_timeout.as_ref() { + Some(timeout) => *timeout, + _ => Duration::from_millis(REQUEST_TIMEOUT), + }); + + let builder = if cache_enabled { + builder.enable_cache() + } else { + builder.disable_cache() + }; + + // request interception is required for all browser.new_page() creations. We also have to use "about:blank" as the base page to setup the listeners and navigate afterwards or the request will hang. + let builder = if intercept { + builder.enable_request_intercept() + } else { + builder + }; + + let builder = match proxies { + Some(proxies) => { + let mut chrome_args = Vec::from(CHROME_ARGS.map(|e| e.replace("://", "=").to_string())); + if use_chrome_ai { + patch_chrome_ai_args(&mut chrome_args); + } + let base_proxies = proxies + .iter() + .filter_map(|p| { + if p.ignore == crate::configuration::ProxyIgnore::Chrome { + None + } else { + Some(p.addr.to_owned()) + } + }) + .collect::>(); + + if !base_proxies.is_empty() { + chrome_args.push(string_concat!(r#"--proxy-server="#, base_proxies.join(";"))); + } + + builder.args(chrome_args) + } + _ => { + if use_chrome_ai { + let mut chrome_args: Vec = + CHROME_ARGS.iter().map(|e| e.to_string()).collect(); + patch_chrome_ai_args(&mut chrome_args); + builder.args(chrome_args) + } else { + builder.args(CHROME_ARGS) + } + } + }; + let builder = match get_detect_chrome_executable() { + Some(v) => builder.chrome_executable(v), + _ => builder, + }; + + match builder.viewport(viewport).build() { + Ok(b) => Some(b), + Err(error) => { + log("", error); + None + } + } +} + +/// get chrome configuration headful +#[cfg(feature = "chrome_headed")] +pub fn get_browser_config( + proxies: &Option>, + intercept: bool, + cache_enabled: bool, + viewport: impl Into>, + request_timeout: &Option, + use_chrome_ai: bool, +) -> Option { + let builder = BrowserConfig::builder() + .disable_default_args() + .no_sandbox() + .request_timeout(match request_timeout.as_ref() { + Some(timeout) => *timeout, + _ => Duration::from_millis(REQUEST_TIMEOUT), + }) + .with_head(); + + let builder = if cache_enabled { + builder.enable_cache() + } else { + builder.disable_cache() + }; + + let builder = if intercept { + builder.enable_request_intercept() + } else { + builder + }; + + let mut chrome_args = Vec::from(CHROME_ARGS.map(|e| { + if e == "--headless" { + "".to_string() + } else { + e.replace("://", "=").to_string() + } + })); + + if use_chrome_ai { + patch_chrome_ai_args(&mut chrome_args); + } + + let builder = match proxies { + Some(proxies) => { + let base_proxies = proxies + .iter() + .filter_map(|p| { + if p.ignore == crate::configuration::ProxyIgnore::Chrome { + None + } else { + Some(p.addr.to_owned()) + } + }) + .collect::>(); + + chrome_args.push(string_concat!(r#"--proxy-server="#, base_proxies.join(";"))); + + builder.args(chrome_args) + } + _ => builder.args(chrome_args), + }; + let builder = match get_detect_chrome_executable() { + Some(v) => builder.chrome_executable(v), + _ => builder, + }; + match builder.viewport(viewport).build() { + Ok(b) => Some(b), + Err(error) => { + log("", error); + None + } + } +} + +/// create the browser handler configuration +fn create_handler_config(config: &Configuration) -> HandlerConfig { + HandlerConfig { + request_timeout: match config.request_timeout.as_ref() { + Some(timeout) => *timeout, + _ => Duration::from_millis(REQUEST_TIMEOUT), + }, + request_intercept: config.chrome_intercept.enabled, + cache_enabled: config.cache, + service_worker_enabled: config.service_worker_enabled, + viewport: match config.viewport { + Some(ref v) => Some(chromiumoxide::handler::viewport::Viewport::from( + v.to_owned(), + )), + _ => default_viewport(), + }, + ignore_visuals: config.chrome_intercept.block_visuals, + whitelist_patterns: config.chrome_intercept.whitelist_patterns.clone(), + blacklist_patterns: config.chrome_intercept.blacklist_patterns.clone(), + ignore_ads: config.chrome_intercept.block_ads, + ignore_javascript: config.chrome_intercept.block_javascript, + ignore_analytics: config.chrome_intercept.block_analytics, + ignore_stylesheets: config.chrome_intercept.block_stylesheets, + extra_headers: match &config.headers { + Some(headers) => { + let mut hm = crate::utils::header_utils::header_map_to_hash_map(headers.inner()); + + cleanup_invalid_headers(&mut hm); + + if hm.is_empty() { + None + } else { + if cfg!(feature = "real_browser") { + crate::utils::header_utils::rewrite_headers_to_title_case(&mut hm); + } + Some(hm) + } + } + _ => None, + }, + intercept_manager: config.chrome_intercept.intercept_manager, + only_html: config.only_html && !config.full_resources, + max_bytes_allowed: config.max_bytes_allowed, + ..HandlerConfig::default() + } +} + +lazy_static! { + static ref CHROM_BASE: Option = std::env::var("CHROME_URL").ok(); +} + +/// Get the default viewport +#[cfg(not(feature = "real_browser"))] +pub fn default_viewport() -> Option { + None +} + +/// Get the default viewport +#[cfg(feature = "real_browser")] +pub fn default_viewport() -> Option { + use super::chrome_viewport::get_random_viewport; + Some(chromiumoxide::handler::viewport::Viewport::from( + get_random_viewport(), + )) +} + +/// Cleanup the headermap. +pub fn cleanup_invalid_headers(hm: &mut std::collections::HashMap) { + hm.remove("User-Agent"); + hm.remove("user-agent"); + hm.remove("host"); + hm.remove("Host"); + hm.remove("connection"); + hm.remove("Connection"); + hm.remove("content-length"); + hm.remove("Content-Length"); +} + +/// Setup the browser configuration. +pub async fn setup_browser_configuration( + config: &Configuration, +) -> Option<(Browser, chromiumoxide::Handler)> { + let proxies = &config.proxies; + + let chrome_connection = if config.chrome_connection_url.is_some() { + config.chrome_connection_url.as_ref() + } else { + CHROM_BASE.as_ref() + }; + + match chrome_connection { + Some(v) => { + let mut attempts = 0; + let max_retries = 10; + let mut browser = None; + + // Attempt reconnections for instances that may be on load balancers (LBs) + // experiencing shutdowns or degradation. This logic implements a retry + // mechanism to improve robustness by allowing multiple attempts to establish. + while attempts <= max_retries { + match Browser::connect_with_config(v, create_handler_config(config)).await { + Ok(b) => { + browser = Some(b); + break; + } + Err(err) => { + log::error!("{:?}", err); + attempts += 1; + if attempts > max_retries { + log::error!("Exceeded maximum retry attempts"); + break; + } + } + } + } + + browser + } + _ => match get_browser_config( + proxies, + config.chrome_intercept.enabled, + config.cache, + match config.viewport { + Some(ref v) => Some(chromiumoxide::handler::viewport::Viewport::from( + v.to_owned(), + )), + _ => default_viewport(), + }, + &config.request_timeout, + config + .remote_multimodal + .as_ref() + .map(|m| m.should_use_chrome_ai()) + .unwrap_or(false), + ) { + Some(mut browser_config) => { + browser_config.ignore_visuals = config.chrome_intercept.block_visuals; + browser_config.ignore_javascript = config.chrome_intercept.block_javascript; + browser_config.ignore_ads = config.chrome_intercept.block_ads; + browser_config.whitelist_patterns = + config.chrome_intercept.whitelist_patterns.clone(); + browser_config.blacklist_patterns = + config.chrome_intercept.blacklist_patterns.clone(); + browser_config.ignore_stylesheets = config.chrome_intercept.block_stylesheets; + browser_config.ignore_analytics = config.chrome_intercept.block_analytics; + browser_config.extra_headers = match &config.headers { + Some(headers) => { + let mut hm = + crate::utils::header_utils::header_map_to_hash_map(headers.inner()); + + cleanup_invalid_headers(&mut hm); + + if hm.is_empty() { + None + } else { + if cfg!(feature = "real_browser") { + crate::utils::header_utils::rewrite_headers_to_title_case(&mut hm); + } + Some(hm) + } + } + _ => None, + }; + browser_config.intercept_manager = config.chrome_intercept.intercept_manager; + browser_config.only_html = config.only_html && !config.full_resources; + + match Browser::launch(browser_config).await { + Ok(browser) => Some(browser), + Err(e) => { + log::error!("Browser::launch() failed: {:?}", e); + None + } + } + } + _ => None, + }, + } +} + +/// Launch a chromium browser with configurations and wait until the instance is up. +pub async fn launch_browser_base( + config: &Configuration, + url_parsed: &Option>, + jar: Option<&std::sync::Arc>, +) -> Option<( + Browser, + tokio::task::JoinHandle<()>, + Option, +)> { + use chromiumoxide::{ + cdp::browser_protocol::target::CreateBrowserContextParams, error::CdpError, + }; + + let browser_configuration = setup_browser_configuration(config).await; + + match browser_configuration { + Some(c) => { + let (mut browser, mut handler) = c; + let mut context_id = None; + + // Spawn a new task that continuously polls the handler + // we might need a select with closing in case handler stalls. + let handle = tokio::task::spawn(async move { + while let Some(k) = handler.next().await { + if let Err(e) = k { + match e { + CdpError::Ws(_) + | CdpError::LaunchExit(_, _) + | CdpError::LaunchTimeout(_) + | CdpError::LaunchIo(_, _) => { + break; + } + _ => { + continue; + } + } + } + } + }); + + let mut create_content = CreateBrowserContextParams::default(); + create_content.dispose_on_detach = Some(true); + + if let Some(ref proxies) = config.proxies { + let use_plain_http = proxies.len() >= 2; + + for proxie in proxies.iter() { + if proxie.ignore == crate::configuration::ProxyIgnore::Chrome { + continue; + } + + let proxie = &proxie.addr; + + if !proxie.is_empty() { + // pick the socks:// proxy over http if found. + if proxie.starts_with("socks://") { + create_content.proxy_server = + Some(proxie.replacen("socks://", "http://", 1)); + // pref this connection + if use_plain_http { + break; + } + } + + if *LOOP_BACK_PROXY && proxie.starts_with("http://localhost") { + create_content.proxy_bypass_list = + // https://source.chromium.org/chromium/chromium/src/+/main:net/proxy_resolution/proxy_bypass_rules.cc + Some("<-loopback>;localhost;[::1]".into()); + } + + create_content.proxy_server = Some(proxie.into()); + } + } + } + + if let Ok(c) = browser.create_browser_context(create_content).await { + let _ = browser.send_new_context(c.clone()).await; + let _ = context_id.insert(c); + if let Some(jar) = jar { + set_cookies(jar, config, url_parsed, &browser).await; + } + if let Some(id) = &browser.browser_context.id { + let cmd = SetDownloadBehaviorParamsBuilder::default(); + + if let Ok(cmd) = cmd + .behavior(SetDownloadBehaviorBehavior::Deny) + .events_enabled(false) + .browser_context_id(id.clone()) + .build() + { + let _ = browser.execute(cmd).await; + } + } + } else { + handle.abort(); + } + + Some((browser, handle, context_id)) + } + _ => None, + } +} + +/// Launch a chromium browser with configurations and wait until the instance is up. +pub async fn launch_browser( + config: &Configuration, + url_parsed: &Option>, +) -> Option<( + Browser, + tokio::task::JoinHandle<()>, + Option, +)> { + launch_browser_base(config, url_parsed, None).await +} + +/// Launch a chromium browser with configurations and wait until the instance is up. +pub async fn launch_browser_cookies( + config: &Configuration, + url_parsed: &Option>, + jar: Option<&Arc>, +) -> Option<( + Browser, + tokio::task::JoinHandle<()>, + Option, +)> { + launch_browser_base(config, url_parsed, jar).await +} + +/// Represents IP-based geolocation and network metadata. +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GeoInfo { + /// The public IP address detected. + pub ip: Option, + /// The CIDR network range of the IP. + pub network: Option, + /// IP version (e.g., "IPv4" or "IPv6"). + pub version: Option, + /// The city associated with the IP. + pub city: Option, + /// The region (e.g., state or province). + pub region: Option, + /// Short regional code (e.g., "CA"). + pub region_code: Option, + /// Two-letter country code (e.g., "US"). + pub country: Option, + /// Full country name. + pub country_name: Option, + /// Same as `country`, often redundant. + pub country_code: Option, + /// ISO 3166-1 alpha-3 country code (e.g., "USA"). + pub country_code_iso3: Option, + /// Capital of the country. + pub country_capital: Option, + /// Top-level domain of the country (e.g., ".us"). + pub country_tld: Option, + /// Continent code (e.g., "NA"). + pub continent_code: Option, + /// Whether the country is in the European Union. + pub in_eu: Option, + /// Postal or ZIP code. + pub postal: Option, + /// Approximate latitude of the IP location. + pub latitude: Option, + /// Approximate longitude of the IP location. + pub longitude: Option, + /// Timezone identifier (e.g., "America/New_York"). + pub timezone: Option, + /// UTC offset string (e.g., "-0400"). + pub utc_offset: Option, + /// Country calling code (e.g., "+1"). + pub country_calling_code: Option, + /// ISO 4217 currency code (e.g., "USD"). + pub currency: Option, + /// Currency name (e.g., "Dollar"). + pub currency_name: Option, + /// Comma-separated preferred language codes. + pub languages: Option, + /// Country surface area in square kilometers. + pub country_area: Option, + /// Approximate country population. + pub country_population: Option, + /// ASN (Autonomous System Number) of the IP. + pub asn: Option, + /// ISP or organization name. + pub org: Option, +} + +/// Auto-detect the geo-location. +#[cfg(feature = "serde")] +pub async fn detect_geo_info(new_page: &Page) -> Option { + use rand::prelude::IndexedRandom; + let apis = [ + "https://ipapi.co/json", + "https://ipinfo.io/json", + "https://ipwho.is/", + ]; + + let url = apis.choose(&mut rand::rng())?; + + new_page.goto(*url).await.ok()?; + new_page.wait_for_navigation().await.ok()?; + + let html = new_page.content().await.ok()?; + + let json_start = html.find("
")? + "
".len();
+    let json_end = html.find("
")?; + let json = html.get(json_start..json_end)?.trim(); + + serde_json::from_str(json).ok() +} + +#[cfg(not(feature = "serde"))] +/// Auto-detect the geo-location. +pub async fn detect_geo_info(new_page: &Page) -> Option { + None +} + +/// configure the browser. +pub async fn configure_browser(new_page: &Page, configuration: &Configuration) { + let mut timezone = configuration.timezone_id.is_some(); + let mut locale = configuration.locale.is_some(); + + let mut timezone_value = configuration.timezone_id.clone(); + let mut locale_value = configuration.locale.clone(); + + let mut emulate_geolocation = None; + + // get the locale of the proxy. + if configuration.auto_geolocation && configuration.proxies.is_some() && !timezone && !locale { + if let Some(geo) = detect_geo_info(new_page).await { + if let Some(languages) = geo.languages { + if let Some(locale_v) = languages.split(',').next() { + if !locale_v.is_empty() { + locale_value = Some(Box::new(locale_v.into())); + } + } + } + + if let Some(timezone_v) = geo.timezone { + if !timezone_v.is_empty() { + timezone_value = Some(Box::new(timezone_v)); + } + } + + timezone = timezone_value.is_some(); + locale = locale_value.is_some(); + + let mut geo_location_override = SetGeolocationOverrideParams::default(); + + geo_location_override.latitude = geo.latitude; + geo_location_override.longitude = geo.longitude; + geo_location_override.accuracy = Some(0.7); + + emulate_geolocation = Some(geo_location_override); + } + } + + if timezone && locale { + let geo = async { + if let Some(geolocation) = emulate_geolocation { + let _ = new_page.emulate_geolocation(geolocation).await; + } + }; + let timezone_id = async { + if let Some(timezone_id) = timezone_value.as_deref() { + if !timezone_id.is_empty() { + let _ = new_page + .emulate_timezone( + chromiumoxide::cdp::browser_protocol::emulation::SetTimezoneOverrideParams::new( + timezone_id, + ), + ) + .await; + } + } + }; + + let locale = async { + if let Some(locale) = locale_value.as_deref() { + if !locale.is_empty() { + let _ = new_page + .emulate_locale( + chromiumoxide::cdp::browser_protocol::emulation::SetLocaleOverrideParams { + locale: Some(locale.into()), + }, + ) + .await; + } + } + }; + + tokio::join!(timezone_id, locale, geo); + } else if timezone { + if let Some(timezone_id) = timezone_value.as_deref() { + if !timezone_id.is_empty() { + let _ = new_page + .emulate_timezone( + chromiumoxide::cdp::browser_protocol::emulation::SetTimezoneOverrideParams::new( + timezone_id, + ), + ) + .await; + } + } + } else if locale { + if let Some(locale) = locale_value.as_deref() { + if !locale.is_empty() { + let _ = new_page + .emulate_locale( + chromiumoxide::cdp::browser_protocol::emulation::SetLocaleOverrideParams { + locale: Some(locale.into()), + }, + ) + .await; + } + } + } +} + +/// attempt to navigate to a page respecting the request timeout. This will attempt to get a response for up to 60 seconds. There is a bug in the browser hanging if the CDP connection or handler errors. [https://github.com/mattsse/chromiumoxide/issues/64] +#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] +pub(crate) async fn attempt_navigation( + url: &str, + browser: &Browser, + request_timeout: &Option, + browser_context_id: &Option, + viewport: &Option, +) -> Result { + let mut cdp_params = CreateTargetParams::new(url); + + cdp_params.background = Some(browser_context_id.is_some()); // not supported headless-shell + cdp_params.browser_context_id.clone_from(browser_context_id); + cdp_params.for_tab = Some(false); + + if viewport.is_some() { + browser + .config() + .and_then(|c| c.viewport.as_ref()) + .and_then(|b_vp| { + viewport.as_ref().map(|vp| { + let new_viewport = b_vp.width == vp.width && b_vp.height == vp.height; + + if !new_viewport { + if vp.width >= 25 { + cdp_params.width = Some(vp.width.into()); + } + if vp.height >= 25 { + cdp_params.height = Some(vp.height.into()); + } + cdp_params.new_window = Some(true); + } + }) + }); + } + + let page_result = tokio::time::timeout( + match request_timeout { + Some(timeout) => *timeout, + _ => tokio::time::Duration::from_secs(60), + }, + browser.new_page(cdp_params), + ) + .await; + + match page_result { + Ok(page) => page, + Err(_) => Err(CdpError::Timeout), + } +} + +/// close the browser and open handles +pub async fn close_browser( + browser_handle: JoinHandle<()>, + _browser: &Browser, + _context_id: &mut Option, +) { + if !browser_handle.is_finished() { + browser_handle.abort(); + } +} + +/// Setup interception for auth challenges. This does nothing without the 'chrome_intercept' flag. +#[cfg(feature = "chrome")] +pub async fn setup_auth_challenge_response( + page: &chromiumoxide::Page, + chrome_intercept: bool, + auth_challenge_response: &Option, +) { + if chrome_intercept { + if let Some(ref auth_challenge_response) = auth_challenge_response { + if let Ok(mut rp) = page + .event_listener::() + .await + { + let intercept_page = page.clone(); + let auth_challenge_response = auth_challenge_response.clone(); + + // we may need return for polling + crate::utils::spawn_task("auth_interception", async move { + while let Some(event) = rp.next().await { + let u = &event.request.url; + let acr = chromiumoxide::cdp::browser_protocol::fetch::AuthChallengeResponse::from(auth_challenge_response.clone()); + + match chromiumoxide::cdp::browser_protocol::fetch::ContinueWithAuthParams::builder() + .request_id(event.request_id.clone()) + .auth_challenge_response(acr) + .build() { + Ok(c) => { + if let Err(e) = intercept_page.send_command(c).await + { + log("Failed to fullfill auth challege request: ", e.to_string()); + } + } + _ => { + log("Failed to get auth challege request handle ", u); + } + } + } + }); + } + } + } +} + +/// Setup interception for chrome request. This does nothing without the 'chrome_intercept' flag. +#[cfg(feature = "chrome")] +pub async fn setup_chrome_interception_base( + page: &chromiumoxide::Page, + chrome_intercept: bool, + auth_challenge_response: &Option, + _ignore_visuals: bool, + _host_name: &str, +) -> Option> { + if chrome_intercept { + setup_auth_challenge_response(page, chrome_intercept, auth_challenge_response).await; + } + None +} + +/// establish all the page events. +pub async fn setup_chrome_events(chrome_page: &chromiumoxide::Page, config: &Configuration) { + let ua_opt = config.user_agent.as_deref().filter(|ua| !ua.is_empty()); + + let ua_for_profiles: &str = ua_opt.map_or("", |v| v); + + let mut emulation_config = + spider_fingerprint::EmulationConfiguration::setup_defaults(ua_for_profiles); + + let stealth_mode = config.stealth_mode; + let use_stealth = stealth_mode.stealth(); + let block_ads = config.chrome_intercept.block_ads; + + emulation_config.dismiss_dialogs = config.dismiss_dialogs.unwrap_or(true); + emulation_config.fingerprint = config.fingerprint; + emulation_config.tier = stealth_mode; + emulation_config.user_agent_data = Some(!ua_for_profiles.is_empty()); + + let viewport = config.viewport.as_ref().map(|vp| (*vp).into()); + + let gpu_profile = spider_fingerprint::profiles::gpu::select_random_gpu_profile( + spider_fingerprint::get_agent_os(ua_for_profiles), + ); + + let merged_script = spider_fingerprint::emulate_with_profile( + ua_for_profiles, + &emulation_config, + &viewport.as_ref(), + &config.evaluate_on_new_document, + gpu_profile, + ); + + let should_inject_script = + (use_stealth || config.evaluate_on_new_document.is_some()) && merged_script.is_some(); + + let hc: u32 = gpu_profile.hardware_concurrency.try_into().unwrap_or(8); + + let apply_page_setup = { + async move { + let f_script = async { + if should_inject_script { + let _ = chrome_page + .add_script_to_evaluate_on_new_document(merged_script) + .await; + } + }; + + let f_adblock = async { + if block_ads { + let _ = chrome_page.set_ad_blocking_enabled(true).await; + } + }; + + let f_ua = async { + if !ua_for_profiles.is_empty() { + let _ = chrome_page.set_user_agent(ua_for_profiles).await; + } + }; + + let f_hc = async { + if use_stealth { + let _ = chrome_page.emulate_hardware_concurrency(hc.into()).await; + } + }; + + tokio::join!(f_script, f_adblock, f_ua, f_hc); + } + }; + + let disable_log = async { + if config.disable_log { + let _ = chrome_page.disable_log().await; + } + }; + + let bypass_csp = async { + if config.bypass_csp { + let _ = chrome_page.set_bypass_csp(true).await; + } + }; + + if tokio::time::timeout(tokio::time::Duration::from_secs(15), async { + tokio::join!( + apply_page_setup, + disable_log, + bypass_csp, + configure_browser(chrome_page, config), + ) + }) + .await + .is_err() + { + log::error!("failed to setup event handlers within 15 seconds."); + } +} + +pub(crate) type BrowserControl = ( + std::sync::Arc, + Option>, + Option, +); + +/// Once cell browser +#[cfg(all(feature = "smart", not(feature = "decentralized")))] +pub(crate) type OnceBrowser = tokio::sync::OnceCell>; + +/// Create the browser controller to auto drop connections. +pub struct BrowserController { + /// The browser. + pub browser: BrowserControl, + /// Closed browser. + pub closed: bool, +} + +impl BrowserController { + /// A new browser controller. + pub(crate) fn new(browser: BrowserControl) -> Self { + BrowserController { + browser, + closed: false, + } + } + /// Dispose the browser context and join handler. + pub fn dispose(&mut self) { + if !self.closed { + self.closed = true; + if let Some(handler) = self.browser.1.take() { + handler.abort(); + } + } + } +} + +impl Drop for BrowserController { + fn drop(&mut self) { + self.dispose(); + } +} diff --git a/spider/src/features/chrome_args.rs b/spider/src/features/chrome_args.rs new file mode 100644 index 000000000..08460473e --- /dev/null +++ b/spider/src/features/chrome_args.rs @@ -0,0 +1,211 @@ +/// static chrome arguments to start +#[cfg(all(feature = "chrome_cpu", feature = "real_browser"))] +pub(crate) static CHROME_ARGS: [&str; 27] = [ + if cfg!(feature = "chrome_headless_new") { + "--headless=new" + } else { + "--headless" + }, + "--disable-extensions", + "--disable-component-extensions-with-background-pages", + "--disable-background-networking", + "--disable-component-update", + "--disable-client-side-phishing-detection", + "--disable-sync", + "--metrics-recording-only", + "--disable-default-apps", + "--mute-audio", + "--no-default-browser-check", + "--no-first-run", + "--disable-gpu", + "--disable-gpu-sandbox", + "--disable-setuid-sandbox", + "--disable-dev-shm-usage", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", + "--disable-background-timer-throttling", + "--disable-ipc-flooding-protection", + "--password-store=basic", + "--use-mock-keychain", + "--force-fieldtrials=*BackgroundTracing/default/", + "--disable-hang-monitor", + "--disable-prompt-on-repost", + "--disable-domain-reliability", + "--disable-features=InterestFeedContentSuggestions,PrivacySandboxSettings4,AutofillServerCommunication,CalculateNativeWinOcclusion,OptimizationHints,AudioServiceOutOfProcess,IsolateOrigins,site-per-process,ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate" +]; + +/// static chrome arguments to start +#[cfg(all( + not(feature = "chrome_cpu"), + feature = "chrome", + feature = "real_browser" +))] +pub(crate) static CHROME_ARGS: [&str; 24] = [ + if cfg!(feature = "chrome_headless_new") { + "--headless=new" + } else { + "--headless" + }, + "--disable-extensions", + "--disable-component-extensions-with-background-pages", + "--disable-background-networking", + "--disable-component-update", + "--disable-client-side-phishing-detection", + "--disable-sync", + "--disable-dev-shm-usage", + "--metrics-recording-only", + "--disable-default-apps", + "--mute-audio", + "--no-default-browser-check", + "--no-first-run", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", + "--disable-background-timer-throttling", + "--disable-ipc-flooding-protection", + "--password-store=basic", + "--use-mock-keychain", + "--force-fieldtrials=*BackgroundTracing/default/", + "--disable-hang-monitor", + "--disable-prompt-on-repost", + "--disable-domain-reliability", + "--disable-features=InterestFeedContentSuggestions,PrivacySandboxSettings4,AutofillServerCommunication,CalculateNativeWinOcclusion,OptimizationHints,AudioServiceOutOfProcess,IsolateOrigins,site-per-process,ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate" +]; + +// One of the configs below is detected by CF bots. We need to take a look at the optimal args 03/25/24. +#[cfg(all(not(feature = "chrome_cpu"), not(feature = "real_browser")))] +/// static chrome arguments to start application ref [https://github.com/a11ywatch/chrome/blob/main/src/main.rs#L13] +pub(crate) static CHROME_ARGS: [&'static str; 60] = [ + if cfg!(feature = "chrome_headless_new") { "--headless=new" } else { "--headless" }, + "--no-sandbox", + "--no-first-run", + "--hide-scrollbars", + // "--allow-pre-commit-input", + // "--user-data-dir=~/.config/google-chrome", + "--allow-running-insecure-content", + "--autoplay-policy=user-gesture-required", + "--ignore-certificate-errors", + "--no-default-browser-check", + "--no-zygote", + "--disable-setuid-sandbox", + "--disable-dev-shm-usage", // required or else docker containers may crash not enough memory + "--disable-threaded-scrolling", + "--disable-demo-mode", + "--disable-dinosaur-easter-egg", + "--disable-fetching-hints-at-navigation-start", + "--disable-site-isolation-trials", + "--disable-web-security", + "--disable-threaded-animation", + "--disable-sync", + "--disable-print-preview", + "--disable-partial-raster", + "--disable-in-process-stack-traces", + "--disable-v8-idle-tasks", + "--disable-low-res-tiling", + "--disable-speech-api", + "--disable-smooth-scrolling", + "--disable-default-apps", + "--disable-prompt-on-repost", + "--disable-domain-reliability", + "--disable-component-update", + "--disable-background-timer-throttling", + "--disable-breakpad", + "--disable-software-rasterizer", + "--disable-extensions", + "--disable-popup-blocking", + "--disable-hang-monitor", + "--disable-image-animation-resync", + "--disable-client-side-phishing-detection", + "--disable-component-extensions-with-background-pages", + "--disable-ipc-flooding-protection", + "--disable-background-networking", + "--disable-renderer-backgrounding", + "--disable-field-trial-config", + "--disable-back-forward-cache", + "--disable-backgrounding-occluded-windows", + "--force-fieldtrials=*BackgroundTracing/default/", + // "--enable-automation", + "--log-level=3", + "--enable-logging=stderr", + "--enable-features=SharedArrayBuffer,NetworkService,NetworkServiceInProcess", + "--metrics-recording-only", + "--use-mock-keychain", + "--force-color-profile=srgb", + "--mute-audio", + "--no-service-autorun", + "--password-store=basic", + "--export-tagged-pdf", + "--no-pings", + "--use-gl=swiftshader", + "--window-size=1920,1080", + "--disable-features=InterestFeedContentSuggestions,PrivacySandboxSettings4,AutofillServerCommunication,CalculateNativeWinOcclusion,OptimizationHints,AudioServiceOutOfProcess,IsolateOrigins,site-per-process,ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate" +]; + +#[cfg(all(feature = "chrome_cpu", not(feature = "real_browser")))] +/// static chrome arguments to start application ref [https://github.com/a11ywatch/chrome/blob/main/src/main.rs#L13] +pub(crate) static CHROME_ARGS: [&'static str; 62] = [ + if cfg!(feature = "chrome_headless_new") { "--headless=new" } else { "--headless" }, + "--no-sandbox", + "--no-first-run", + "--hide-scrollbars", + // "--allow-pre-commit-input", + // "--user-data-dir=~/.config/google-chrome", + "--allow-running-insecure-content", + "--autoplay-policy=user-gesture-required", + "--ignore-certificate-errors", + "--no-default-browser-check", + "--no-zygote", + "--in-process-gpu", + "--disable-gpu", + "--disable-gpu-sandbox", + "--disable-setuid-sandbox", + "--disable-dev-shm-usage", // required or else docker containers may crash not enough memory + "--disable-threaded-scrolling", + "--disable-demo-mode", + "--disable-dinosaur-easter-egg", + "--disable-fetching-hints-at-navigation-start", + "--disable-site-isolation-trials", + "--disable-web-security", + "--disable-threaded-animation", + "--disable-sync", + "--disable-print-preview", + "--disable-in-process-stack-traces", + "--disable-v8-idle-tasks", + "--disable-low-res-tiling", + "--disable-speech-api", + "--disable-smooth-scrolling", + "--disable-default-apps", + "--disable-prompt-on-repost", + "--disable-domain-reliability", + "--disable-component-update", + "--disable-background-timer-throttling", + "--disable-breakpad", + "--disable-software-rasterizer", + "--disable-extensions", + "--disable-popup-blocking", + "--disable-hang-monitor", + "--disable-image-animation-resync", + "--disable-client-side-phishing-detection", + "--disable-component-extensions-with-background-pages", + "--disable-ipc-flooding-protection", + "--disable-background-networking", + "--disable-renderer-backgrounding", + "--disable-field-trial-config", + "--disable-back-forward-cache", + "--disable-backgrounding-occluded-windows", + "--force-fieldtrials=*BackgroundTracing/default/", + // "--enable-automation", + "--log-level=3", + "--enable-logging=stderr", + "--enable-features=SharedArrayBuffer,NetworkService,NetworkServiceInProcess", + "--metrics-recording-only", + "--use-mock-keychain", + "--force-color-profile=srgb", + "--mute-audio", + "--no-service-autorun", + "--password-store=basic", + "--export-tagged-pdf", + "--no-pings", + "--use-gl=swiftshader", + "--window-size=1920,1080", + "--disable-features=InterestFeedContentSuggestions,PrivacySandboxSettings4,AutofillServerCommunication,CalculateNativeWinOcclusion,OptimizationHints,AudioServiceOutOfProcess,IsolateOrigins,site-per-process,ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate" +]; diff --git a/spider/src/features/chrome_common.rs b/spider/src/features/chrome_common.rs new file mode 100644 index 000000000..80082e562 --- /dev/null +++ b/spider/src/features/chrome_common.rs @@ -0,0 +1,1260 @@ +use crate::utils::trie::Trie; + +#[cfg(feature = "chrome")] +use chromiumoxide::handler::blockers::NetworkInterceptManager; + +/// wrapper for non chrome interception. does nothing. +#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[cfg(not(feature = "chrome"))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum NetworkInterceptManager { + #[default] + /// Unknown + Unknown, +} + +#[cfg(not(feature = "chrome"))] +impl NetworkInterceptManager { + /// a custom intercept handle. + pub fn new(_url: &Option>) -> NetworkInterceptManager { + NetworkInterceptManager::Unknown + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Wait for network request. This does nothing without the `chrome` flag enabled. +pub struct WaitForIdleNetwork { + /// The max time to wait for the network. It is recommended to set this to a value around 30s. Set the value to None to remove the timeout. + pub timeout: Option, +} + +impl WaitForIdleNetwork { + /// Create new WaitForIdleNetwork with timeout. + pub fn new(timeout: Option) -> Self { + Self { timeout } + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Wait for a selector with optional timeout. This does nothing without the `chrome` flag enabled. +pub struct WaitForSelector { + /// The max time to wait for the selector. It is recommended to set this to a value around 30s. Set the value to None to remove the timeout. + pub timeout: Option, + /// The selector wait for + pub selector: String, +} + +impl WaitForSelector { + /// Create new WaitForSelector with timeout. + pub fn new(timeout: Option, selector: String) -> Self { + Self { timeout, selector } + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Wait for with a delay. Should only be used for testing purposes. This does nothing without the `chrome` flag enabled. +pub struct WaitForDelay { + /// The max time to wait. It is recommended to set this to a value around 30s. Set the value to None to remove the timeout. + pub timeout: Option, +} + +impl WaitForDelay { + /// Create new WaitForDelay with timeout. + pub fn new(timeout: Option) -> Self { + Self { timeout } + } +} + +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// The wait for options for the page. Multiple options can be set. This does nothing without the `chrome` flag enabled. +pub struct WaitFor { + /// The max time to wait for the selector. + pub selector: Option, + /// Wait for network request to be idle within a time frame period (500ms no network connections). This does nothing without the `chrome` flag enabled. + pub idle_network: Option, + /// Wait for network request with a max timeout. This does nothing without the `chrome` flag enabled. + pub idle_network0: Option, + /// Wait for network to be almost idle with a max timeout. This does nothing without the `chrome` flag enabled. + pub almost_idle_network0: Option, + /// Wait for delay. Should only be used for testing. + pub delay: Option, + /// Wait for dom element to stop updating. + pub dom: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// Wait for page navigations. + pub page_navigations: bool, +} + +impl WaitFor { + /// Create new WaitFor with timeout. + pub fn new( + timeout: Option, + delay: Option, + page_navigations: bool, + idle_network: bool, + selector: Option, + dom: Option, + ) -> Self { + Self { + page_navigations, + idle_network: if idle_network { + Some(WaitForIdleNetwork::new(timeout)) + } else { + None + }, + idle_network0: None, + almost_idle_network0: None, + selector: if selector.is_some() { + Some(WaitForSelector::new(timeout, selector.unwrap_or_default())) + } else { + None + }, + delay, + dom, + } + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, Hash, Default, strum::EnumString, strum::Display, strum::AsRefStr, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Capture screenshot options for chrome. +pub enum CaptureScreenshotFormat { + #[cfg_attr(feature = "serde", serde(rename = "jpeg"))] + /// jpeg format + Jpeg, + #[cfg_attr(feature = "serde", serde(rename = "png"))] + #[default] + /// png format + Png, + #[cfg_attr(feature = "serde", serde(rename = "webp"))] + /// webp format + Webp, +} + +#[cfg(feature = "chrome")] +impl From + for chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat +{ + fn from(format: CaptureScreenshotFormat) -> Self { + match format { + CaptureScreenshotFormat::Jpeg => { + chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Jpeg + } + CaptureScreenshotFormat::Png => { + chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Png + } + CaptureScreenshotFormat::Webp => { + chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Webp + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// View port handling for chrome. +pub struct Viewport { + /// Device screen Width + pub width: u32, + /// Device screen size + pub height: u32, + /// Device scale factor + pub device_scale_factor: Option, + /// Emulating Mobile? + pub emulating_mobile: bool, + /// Use landscape mode instead of portrait. + pub is_landscape: bool, + /// Touch screen device? + pub has_touch: bool, +} + +impl Default for Viewport { + fn default() -> Self { + Viewport { + width: 800, + height: 600, + device_scale_factor: None, + emulating_mobile: false, + is_landscape: false, + has_touch: false, + } + } +} + +impl Viewport { + /// Create a new viewport layout for chrome passing in the width. + pub fn new(width: u32, height: u32) -> Self { + Viewport { + width, + height, + ..Default::default() + } + } + /// Determine if the layout is a mobile device or not to emulate. + pub fn set_mobile(&mut self, emulating_mobile: bool) { + self.emulating_mobile = emulating_mobile; + } + /// Determine if the layout is in landscrape view or not to emulate. + pub fn set_landscape(&mut self, is_landscape: bool) { + self.is_landscape = is_landscape; + } + /// Determine if the device is a touch screen or not to emulate. + pub fn set_touch(&mut self, has_touch: bool) { + self.has_touch = has_touch; + } + /// The scale factor for the screen layout. + pub fn set_scale_factor(&mut self, device_scale_factor: Option) { + self.device_scale_factor = device_scale_factor; + } +} + +#[cfg(feature = "chrome")] +impl From for chromiumoxide::handler::viewport::Viewport { + fn from(viewport: Viewport) -> Self { + Self { + width: viewport.width, + height: viewport.height, + device_scale_factor: viewport.device_scale_factor, + emulating_mobile: viewport.emulating_mobile, + is_landscape: viewport.is_landscape, + has_touch: viewport.has_touch, + } + } +} + +impl From for spider_fingerprint::spoof_viewport::Viewport { + fn from(viewport: Viewport) -> Self { + Self { + width: viewport.width, + height: viewport.height, + device_scale_factor: viewport.device_scale_factor, + emulating_mobile: viewport.emulating_mobile, + is_landscape: viewport.is_landscape, + has_touch: viewport.has_touch, + } + } +} + +#[doc = "Capture page screenshot.\n[captureScreenshot](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot)"] +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CaptureScreenshotParams { + #[doc = "Image compression format (defaults to png)."] + pub format: Option, + #[doc = "Compression quality from range [0..100] (jpeg only)."] + pub quality: Option, + #[doc = "Capture the screenshot of a given region only."] + pub clip: Option, + #[doc = "Capture the screenshot from the surface, rather than the view. Defaults to true."] + pub from_surface: Option, + #[doc = "Capture the screenshot beyond the viewport. Defaults to false."] + pub capture_beyond_viewport: Option, +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// The view port clip for screenshots. +pub struct ClipViewport { + #[doc = "X offset in device independent pixels (dip)."] + #[cfg_attr(feature = "serde", serde(rename = "x"))] + pub x: f64, + #[doc = "Y offset in device independent pixels (dip)."] + #[cfg_attr(feature = "serde", serde(rename = "y"))] + pub y: f64, + #[doc = "Rectangle width in device independent pixels (dip)."] + #[cfg_attr(feature = "serde", serde(rename = "width"))] + pub width: f64, + #[doc = "Rectangle height in device independent pixels (dip)."] + #[cfg_attr(feature = "serde", serde(rename = "height"))] + pub height: f64, + #[doc = "Page scale factor."] + #[cfg_attr(feature = "serde", serde(rename = "scale"))] + pub scale: f64, +} + +#[cfg(feature = "chrome")] +impl From for chromiumoxide::cdp::browser_protocol::page::Viewport { + fn from(viewport: ClipViewport) -> Self { + Self { + x: viewport.x, + y: viewport.y, + height: viewport.height, + width: viewport.width, + scale: viewport.scale, + } + } +} + +/// Screenshot configuration. +#[derive(Debug, Default, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScreenShotConfig { + /// The screenshot params. + pub params: ScreenshotParams, + /// Return the bytes of the screenshot on the Page. + pub bytes: bool, + /// Store the screenshot to disk. This can be used with output_dir. If disabled will not store the file to the output directory. + pub save: bool, + /// The output directory to store the file. Parant folders may be created inside the directory. + pub output_dir: Option, +} + +impl ScreenShotConfig { + /// Create a new screenshot configuration. + pub fn new( + params: ScreenshotParams, + bytes: bool, + save: bool, + output_dir: Option, + ) -> Self { + Self { + params, + bytes, + save, + output_dir, + } + } +} + +/// The screenshot params for the page. +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScreenshotParams { + /// Chrome DevTools Protocol screenshot options. + pub cdp_params: CaptureScreenshotParams, + /// Take full page screenshot. + pub full_page: Option, + /// Make the background transparent (png only). + pub omit_background: Option, +} + +impl ScreenshotParams { + /// Create a new ScreenshotParams. + pub fn new( + cdp_params: CaptureScreenshotParams, + full_page: Option, + omit_background: Option, + ) -> Self { + Self { + cdp_params, + full_page, + omit_background, + } + } +} + +#[cfg(feature = "chrome")] +impl From for chromiumoxide::page::ScreenshotParams { + fn from(params: ScreenshotParams) -> Self { + let full_page = if params.full_page.is_some() { + params.full_page.unwrap_or_default() + } else { + std::env::var("SCREENSHOT_FULL_PAGE").unwrap_or_default() == "true" + }; + let omit_background = if params.omit_background.is_some() { + params.omit_background.unwrap_or_default() + } else { + match std::env::var("SCREENSHOT_OMIT_BACKGROUND") { + Ok(t) => t == "true", + _ => true, + } + }; + let format = if params.cdp_params.format.is_some() { + match params.cdp_params.format { + Some(v) => v.into(), + _ => chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Png, + } + } else { + chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Png + }; + + let params_builder = chromiumoxide::page::ScreenshotParams::builder() + .format(format) + .full_page(full_page) + .omit_background(omit_background); + + let params_builder = if params.cdp_params.quality.is_some() { + params_builder.quality(params.cdp_params.quality.unwrap_or(75)) + } else { + params_builder + }; + + let params_builder = if params.cdp_params.clip.is_some() { + match params.cdp_params.clip { + Some(vp) => params_builder.clip( + chromiumoxide::cdp::browser_protocol::page::Viewport::from(vp), + ), + _ => params_builder, + } + } else { + params_builder + }; + + let params_builder = if params.cdp_params.capture_beyond_viewport.is_some() { + match params.cdp_params.capture_beyond_viewport { + Some(capture_beyond_viewport) => { + params_builder.capture_beyond_viewport(capture_beyond_viewport) + } + _ => params_builder, + } + } else { + params_builder + }; + + let params_builder = if params.cdp_params.from_surface.is_some() { + match params.cdp_params.from_surface { + Some(from_surface) => params_builder.from_surface(from_surface), + _ => params_builder, + } + } else { + params_builder + }; + + params_builder.build() + } +} + +#[doc = "The decision on what to do in response to the authorization challenge. Default means\ndeferring to the default behavior of the net stack, which will likely either the Cancel\nauthentication or display a popup dialog box."] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AuthChallengeResponseResponse { + #[default] + /// The default. + Default, + /// Cancel the authentication prompt. + CancelAuth, + /// Provide credentials. + ProvideCredentials, +} + +#[doc = "Response to an AuthChallenge.\n[AuthChallengeResponse](https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#type-AuthChallengeResponse)"] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AuthChallengeResponse { + #[doc = "The decision on what to do in response to the authorization challenge. Default means\ndeferring to the default behavior of the net stack, which will likely either the Cancel\nauthentication or display a popup dialog box."] + pub response: AuthChallengeResponseResponse, + #[doc = "The username to provide, possibly empty. Should only be set if response is\nProvideCredentials."] + pub username: Option, + #[doc = "The password to provide, possibly empty. Should only be set if response is\nProvideCredentials."] + pub password: Option, +} + +#[cfg(feature = "chrome")] +impl From + for chromiumoxide::cdp::browser_protocol::fetch::AuthChallengeResponse +{ + fn from(auth_challenge_response: AuthChallengeResponse) -> Self { + Self { + response: match auth_challenge_response.response { + AuthChallengeResponseResponse::CancelAuth => chromiumoxide::cdp::browser_protocol::fetch::AuthChallengeResponseResponse::CancelAuth , + AuthChallengeResponseResponse::ProvideCredentials => chromiumoxide::cdp::browser_protocol::fetch::AuthChallengeResponseResponse::ProvideCredentials , + AuthChallengeResponseResponse::Default => chromiumoxide::cdp::browser_protocol::fetch::AuthChallengeResponseResponse::Default , + + }, + username: auth_challenge_response.username, + password: auth_challenge_response.password + } + } +} + +/// Represents various web automation actions. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum WebAutomation { + /// Runs custom JavaScript code. + Evaluate(String), + /// Clicks on an element. + Click(String), + /// Clicks on all elements. + ClickAll(String), + /// Clicks at the position x and y coordinates. + ClickPoint { + /// The horizontal (X) coordinate. + x: f64, + /// The vertical (Y) coordinate. + y: f64, + }, + /// Click and hold on an element (uses the element clickable point). + ClickHold { + /// The selector of the element to click-hold. + selector: String, + /// How long to hold (ms). + hold_ms: u64, + }, + /// Click and hold at a specific point. + ClickHoldPoint { + /// The horizontal (X) coordinate. + x: f64, + /// The vertical (Y) coordinate. + y: f64, + /// How long to hold (ms). + hold_ms: u64, + }, + /// Click-and-drag from one element to another (uses clickable points). + ClickDrag { + /// Drag start selector + from: String, + /// Drag end selector + to: String, + /// Optional modifier (e.g. 8 for Shift). If None, no modifier. + modifier: Option, + }, + + /// Click-and-drag from one point to another. + ClickDragPoint { + /// Start X + from_x: f64, + /// Start Y + from_y: f64, + /// End X + to_x: f64, + /// End Y + to_y: f64, + /// Optional modifier (e.g. 8 for Shift). If None, no modifier. + modifier: Option, + }, + /// Clicks on all elements. + ClickAllClickable(), + /// Waits for a fixed duration in milliseconds. + Wait(u64), + /// Waits for the next navigation event. + WaitForNavigation, + /// Wait for dom updates to stop. + WaitForDom { + /// The selector of the element to wait for updates. + selector: Option, + /// The timeout to wait for in ms. + timeout: u32, + }, + /// Waits for an element to appear. + WaitFor(String), + /// Waits for an element to appear with a timeout. + WaitForWithTimeout { + /// The selector of the element to wait for updates. + selector: String, + /// The timeout to wait for in ms. + timeout: u64, + }, + /// Waits for an element to appear and then clicks on it. + WaitForAndClick(String), + /// Scrolls the screen in the horizontal axis by a specified amount in pixels. + ScrollX(i32), + /// Scrolls the screen in the vertical axis by a specified amount in pixels. + ScrollY(i32), + /// Fills an input element with a specified value. + Fill { + /// The selector of the input element to fill. + selector: String, + /// The value to fill the input element with. + value: String, + }, + /// Type with the keyboard. + Type { + /// The value to fill the input element with. + value: String, + /// The mofidier to use for the key. + modifier: Option, + }, + /// Scrolls the page until the end. + InfiniteScroll(u32), + /// Perform a screenshot on the page - fullscreen and omit background for params. + Screenshot { + /// Take a full page screenshot. + full_page: bool, + /// Omit the background. + omit_background: bool, + /// The output file to store the screenshot. + output: String, + }, + /// Only continue to the next automation if the prior step was valid. Use this intermediate after a step to break out of the chain. + ValidateChain, +} + +impl WebAutomation { + /// Machine-friendly variant name (no params). + pub fn name(&self) -> &'static str { + use WebAutomation::*; + match self { + Evaluate(_) => "Evaluate", + Click(_) => "Click", + ClickDrag { .. } => "ClickDrag", + ClickDragPoint { .. } => "ClickDragPoint", + ClickHold { .. } => "ClickHold", + ClickHoldPoint { .. } => "ClickHoldPoint", + ClickAll(_) => "ClickAll", + ClickAllClickable() => "ClickAllClickable", + ClickPoint { .. } => "ClickPoint", + Wait(_) => "Wait", + WaitForNavigation => "WaitForNavigation", + WaitForDom { .. } => "WaitForDom", + WaitFor(_) => "WaitFor", + WaitForWithTimeout { .. } => "WaitForWithTimeout", + WaitForAndClick(_) => "WaitForAndClick", + ScrollX(_) => "ScrollX", + ScrollY(_) => "ScrollY", + Fill { .. } => "Fill", + Type { .. } => "Type", + InfiniteScroll(_) => "InfiniteScroll", + Screenshot { .. } => "Screenshot", + ValidateChain => "ValidateChain", + } + } + + /// Optional: human-friendly label with key params. + pub fn label(&self) -> String { + use WebAutomation::*; + match self { + Evaluate(_) => "Evaluate JS".into(), + Click(s) => format!("Click {}", s), + ClickHold { selector, hold_ms } => { + format!("ClickHold {} ({}ms)", selector, hold_ms) + } + ClickHoldPoint { x, y, hold_ms } => { + format!("ClickHoldPoint x:{} y:{} ({}ms)", x, y, hold_ms) + } + ClickDrag { from, to, modifier } => { + format!("ClickDrag {} -> {} modifier={:?}", from, to, modifier) + } + ClickDragPoint { + from_x, + from_y, + to_x, + to_y, + modifier, + } => format!( + "ClickDragPoint ({},{}) -> ({},{}) modifier={:?}", + from_x, from_y, to_x, to_y, modifier + ), + ClickAll(s) => format!("ClickAll {}", s), + ClickAllClickable() => "ClickAllClickable".into(), + Wait(ms) => format!("Wait {}ms", ms), + WaitForNavigation => "WaitForNavigation".into(), + ClickPoint { x, y } => { + format!("ClickPoint x:{} y:{}", x, y) + } + WaitForDom { selector, timeout } => selector + .as_ref() + .map(|s| format!("WaitForDom {} ({}ms)", s, timeout)) + .unwrap_or_else(|| format!("WaitForDom ({}ms)", timeout)), + WaitFor(s) => format!("WaitFor {}", s), + WaitForWithTimeout { selector, timeout } => { + format!("WaitForWithTimeout {} ({}ms)", selector, timeout) + } + WaitForAndClick(s) => format!("WaitForAndClick {}", s), + ScrollX(dx) => format!("ScrollX {}", dx), + ScrollY(dy) => format!("ScrollY {}", dy), + Fill { selector, .. } => format!("Fill {}", selector), + Type { value, modifier } => format!("Type {} modifier={:?}", value, modifier), + InfiniteScroll(n) => format!("InfiniteScroll {}", n), + Screenshot { + full_page, + omit_background, + output, + } => format!( + "Screenshot full={} omit_bg={} -> {}", + full_page, omit_background, output + ), + ValidateChain => "ValidateChain".into(), + } + } +} + +impl core::fmt::Display for WebAutomation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.label()) + } +} + +#[cfg(feature = "chrome")] +/// Generate the wait for Dom function targeting the element. This defaults to using the body. +pub(crate) fn generate_wait_for_dom_js_v2( + timeout_ms: u32, + selector: &str, + quiet_ms: u32, + stable_frames: u32, + require_visible: bool, + require_images_complete: bool, +) -> String { + let t = timeout_ms.min(crate::utils::FIVE_MINUTES); + let q = quiet_ms.max(50).min(t); + let f = stable_frames.max(1).min(10); + let s = if selector.is_empty() { + "body" + } else { + selector + }; + + format!( + r###"(()=>new Promise(R=>{{const S={s:?},T={t},Q={q},F={f},V={vis},I={img},P=["#__next","#__nuxt","#app","#root","main","body"],N=()=>performance.now(),W=e=>{{if(!e)return!1;const t=getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const r=e.getBoundingClientRect();return r.width>0&&r.height>0}},M=e=>{{if(!e)return!1;const t=e.querySelectorAll("img");for(let e=0;e{{let e=document.querySelector(S);if(e)return e;for(let t=0;t{{t&&t.disconnect(),t=new MutationObserver(()=>{{r=N(),o=0}}),t.observe(n,{{subtree:!0,childList:!0,attributes:!0,characterData:!0}})}},i=()=>{{const n=N();if(n-s>=T)return t&&t.disconnect(),void R(!1);(!e||!document.contains(e))&&(e=k())&&((r=n,o=0),a(e));e&&(V&&!W(e)?o=0:I&&!M(e)?o=0:n-r>=Q?(o++,o>=F&&(t&&t.disconnect(),R(!0))):o=0),requestAnimationFrame(i)}};i()}}))()"###, + t = t, + q = q, + f = f, + vis = if require_visible { "true" } else { "false" }, + img = if require_images_complete { + "true" + } else { + "false" + }, + ) +} + +#[cfg(feature = "chrome")] +/// Generate the wait for Dom function targeting the element. This defaults to using the body. +pub(crate) fn generate_wait_for_dom_js_code_with_selector( + timeout: u32, + selector: Option<&str>, +) -> String { + let t = timeout.min(crate::utils::FIVE_MINUTES); + let s = selector.unwrap_or("body"); + format!( + "new Promise((r,j)=>{{const s='{s}',t={t},i=220,n=50;let l=Date.now(),el,o,d,c;const check=()=>{{el=document.querySelector(s);if(!el)return;clearInterval(wait);l=Date.now();o=new MutationObserver(()=>{{l=Date.now();}});o.observe(el,{{childList:!0,subtree:!0,attributes:!0,characterData:!0}});d=setTimeout(()=>{{clearInterval(c),o.disconnect(),j(new Error('Dom Timeout.'))}},t);c=setInterval(()=>{{Date.now()-l>=i&&(clearTimeout(d),clearInterval(c),o.disconnect(),r(!0))}},n);}};const wait=setInterval(check,n);check();}});", + s = s, + t = t + ) +} + +// #[cfg(feature = "chrome")] +// /// Generate the wait for Dom function targeting the element. This defaults to using the body. +// pub(crate) fn generate_wait_for_dom_js_code_with_selector_base( +// timeout: u32, +// selector: &str, +// ) -> String { +// generate_wait_for_dom_js_code_with_selector( +// timeout, +// if selector.is_empty() { +// None +// } else { +// Some(selector) +// }, +// ) +// } + +#[cfg(feature = "chrome")] +const CLICKABLE_SELECTOR: &str = concat!( + "button:not([disabled]),", + "input[type='button']:not([disabled]),", + "input[type='submit']:not([disabled]),", + "input[type='reset']:not([disabled]),", + "input[type='checkbox']:not([disabled]),", + "input[type='radio']:not([disabled]),", + "label[for],", + "summary,", + "select:not([disabled]),", + "textarea:not([disabled]),", + "[role='button']:not([aria-disabled='true']),", + "[onclick],", + "[contenteditable=''],[contenteditable='true'],", + // focusables as a last resort + "[tabindex]:not([tabindex^='-'],a[href])" +); + +impl WebAutomation { + #[cfg(feature = "chrome")] + /// Run the web automation step. + pub async fn run(&self, page: &chromiumoxide::Page) -> bool { + use crate::utils::wait_for_selector; + use std::time::Duration; + + let mut valid = false; + + match self { + WebAutomation::Evaluate(js) => { + valid = page.evaluate(js.as_str()).await.is_ok(); + } + WebAutomation::Click(selector) => { + if let Ok(ele) = page.find_element(selector).await { + valid = ele.click().await.is_ok(); + } + } + + WebAutomation::WaitForWithTimeout { selector, timeout } => { + valid = + wait_for_selector(page, Some(Duration::from_millis(*timeout)), selector).await; + } + WebAutomation::Wait(ms) => { + tokio::time::sleep(Duration::from_millis(*ms)).await; + valid = true; + } + WebAutomation::ClickHold { selector, hold_ms } => { + if let Ok(ele) = page.find_element(selector).await { + if let Ok(pt) = ele.clickable_point().await { + valid = page + .click_and_hold(pt, Duration::from_millis(*hold_ms)) + .await + .is_ok(); + } + } + } + WebAutomation::ClickHoldPoint { x, y, hold_ms } => { + let pt = chromiumoxide::layout::Point { x: *x, y: *y }; + valid = page + .click_and_hold(pt, Duration::from_millis(*hold_ms)) + .await + .is_ok(); + } + WebAutomation::ClickAll(selector) => { + if let Ok(eles) = page.find_elements(selector).await { + for ele in eles { + valid = ele.click().await.is_ok(); + } + } + } + WebAutomation::ClickDrag { from, to, modifier } => { + if let (Ok(from_el), Ok(to_el)) = + (page.find_element(from).await, page.find_element(to).await) + { + if let (Ok(p1), Ok(p2)) = ( + from_el.clickable_point().await, + to_el.clickable_point().await, + ) { + valid = match modifier { + Some(m) => page.click_and_drag_with_modifier(p1, p2, *m).await.is_ok(), + None => page.click_and_drag(p1, p2).await.is_ok(), + }; + } + } + } + + WebAutomation::ClickDragPoint { + from_x, + from_y, + to_x, + to_y, + modifier, + } => { + let p1 = chromiumoxide::layout::Point { + x: *from_x, + y: *from_y, + }; + let p2 = chromiumoxide::layout::Point { x: *to_x, y: *to_y }; + + valid = match modifier { + Some(m) => page.click_and_drag_with_modifier(p1, p2, *m).await.is_ok(), + None => page.click_and_drag(p1, p2).await.is_ok(), + }; + } + WebAutomation::ClickAllClickable() => { + if let Ok(eles) = page.find_elements(CLICKABLE_SELECTOR).await { + for ele in eles { + valid = ele.click().await.is_ok(); + } + } + } + WebAutomation::ClickPoint { x, y } => { + valid = page + .click(chromiumoxide::layout::Point { x: *x, y: *y }) + .await + .is_ok(); + } + WebAutomation::WaitForDom { selector, timeout } => { + valid = page + .evaluate( + generate_wait_for_dom_js_code_with_selector(*timeout, selector.as_deref()) + .as_str(), + ) + .await + .is_ok(); + } + WebAutomation::WaitFor(selector) => { + valid = wait_for_selector(page, Some(Duration::from_secs(60)), selector).await; + } + WebAutomation::WaitForNavigation => { + valid = page.wait_for_navigation().await.is_ok(); + } + WebAutomation::WaitForAndClick(selector) => { + valid = wait_for_selector(page, Some(Duration::from_secs(60)), selector).await; + if let Ok(ele) = page.find_element(selector).await { + valid = ele.click().await.is_ok(); + } + } + WebAutomation::ScrollX(px) => { + valid = page + .scroll_by((*px as f32).into(), 0.0, Default::default()) + .await + .is_ok() + } + WebAutomation::ScrollY(px) => { + valid = page + .scroll_by(0.0, (*px as f32).into(), Default::default()) + .await + .is_ok() + } + WebAutomation::Fill { selector, value } => { + if let Ok(ele) = page.find_element(selector).await { + if let Ok(el) = ele.click().await { + valid = el.type_str(value).await.is_ok(); + } + } + } + WebAutomation::Type { value, modifier } => { + valid = page.type_str_with_modifier(value, *modifier).await.is_ok() + } + WebAutomation::InfiniteScroll(duration) => { + valid = page.evaluate(set_dynamic_scroll(*duration)).await.is_ok(); + } + WebAutomation::Screenshot { + full_page, + omit_background, + output, + } => { + let mut cdp_params: CaptureScreenshotParams = CaptureScreenshotParams::default(); + cdp_params.format = Some(CaptureScreenshotFormat::Png); + + let screenshot_params = + ScreenshotParams::new(cdp_params, Some(*full_page), Some(*omit_background)); + + valid = page + .save_screenshot(screenshot_params, output) + .await + .is_ok(); + } + _ => (), + }; + + valid + } +} + +/// Set a dynamic time to scroll. +pub fn set_dynamic_scroll(timeout: u32) -> String { + let timeout = timeout.min(crate::utils::FIVE_MINUTES); + let s = string_concat!( + r###"document.addEventListener('DOMContentLoaded',e=>{let t=null,o=null,n="###, + timeout.to_string(), + r###",a=Date.now(),i=Date.now(),r=()=>{window.scrollTo(0,document.body.scrollHeight)},l=()=>{o&&o.disconnect(),console.log('Stopped checking for new content.')},c=(e,n)=>{e.forEach(e=>{if(e.isIntersecting){i=Date.now();const n=Date.now();if(n-a>=t||n-i>=1e4)return void l();r(),t=document.querySelector('body > *:last-child'),o.observe(t)}})},s=()=>{t&&(o=new IntersectionObserver(c),o.observe(t))},d=()=>{['load','error','abort'].forEach(e=>{window.addEventListener(e,()=>{const e=document.querySelector('body > *:last-child');e!==t&&(i=Date.now(),t=e,o.observe(t))})})},u=()=>{r(),t=document.querySelector('body > *:last-child'),s(),d()};u(),setTimeout(l,n)});"### + ); + + s +} + +/// Execution scripts to run on the page when using chrome by url. +pub type ExecutionScriptsMap = hashbrown::HashMap; +/// Automation scripts to run on the page when using chrome by url. +pub type AutomationScriptsMap = hashbrown::HashMap>; + +/// Execution scripts to run on the page when using chrome by url. +pub type ExecutionScripts = Trie; +/// Automation scripts to run on the page when using chrome by url. +pub type AutomationScripts = Trie>; + +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Chrome request interception configurations. +pub struct RequestInterceptConfiguration { + /// Request interception enabled? + pub enabled: bool, + /// Block visuals. By default this is enabled. This will prevent Prefetch, Ping, and some javascript from rendering. + pub block_visuals: bool, + /// Block stylesheets. + pub block_stylesheets: bool, + /// Block javascript only allowing critcal framework or lib based javascript to render.. + pub block_javascript: bool, + /// Block analytics. + pub block_analytics: bool, + /// Block ads. Requires the `adblock` feature flag. + pub block_ads: bool, + /// Intercept Manager + pub intercept_manager: NetworkInterceptManager, + /// Whitelist patterns. + pub whitelist_patterns: Option>, + /// Blacklist patterns. + pub blacklist_patterns: Option>, +} + +impl RequestInterceptConfiguration { + /// Setup a new intercept config + pub fn new(enabled: bool) -> RequestInterceptConfiguration { + RequestInterceptConfiguration { + enabled, + block_javascript: false, + block_visuals: true, + block_analytics: true, + block_stylesheets: true, + block_ads: true, + ..Default::default() + } + } + /// Setup a new intercept config with a custom intercept manager. + pub fn new_manager( + enabled: bool, + url: &Option>, + ) -> RequestInterceptConfiguration { + RequestInterceptConfiguration { + enabled, + block_javascript: false, + block_visuals: true, + block_analytics: true, + block_stylesheets: true, + intercept_manager: NetworkInterceptManager::new(url), + ..Default::default() + } + } + + /// Setup the network request manager type. + pub fn setup_intercept_manager(&mut self, url: &Option>) { + self.intercept_manager = NetworkInterceptManager::new(url); + } + + /// Set the whitelist patterns. + pub fn set_whitelist_patterns(&mut self, whitelist_patterns: Option>) { + self.whitelist_patterns = whitelist_patterns; + } + + /// Set the blacklist patterns. + pub fn set_blacklist_patterns(&mut self, blacklist_patterns: Option>) { + self.blacklist_patterns = blacklist_patterns; + } + + /// Block all request besides html and the important stuff. + pub fn block_all(&mut self) -> &Self { + self.block_javascript = true; + self.block_analytics = true; + self.block_stylesheets = true; + self.block_visuals = true; + self.block_ads = true; + self + } +} + +/// Convert ExecutionScripts to Trie. +pub fn convert_to_trie_execution_scripts( + input: &Option, +) -> Option> { + match input { + Some(ref scripts) => { + let mut trie = Trie::new(); + for (path, script) in scripts { + trie.insert(path, script.clone()); + } + Some(trie) + } + None => None, + } +} + +/// Convert AutomationScripts to Trie. +pub fn convert_to_trie_automation_scripts( + input: &Option, +) -> Option>> { + match input { + Some(ref scripts) => { + let mut trie = Trie::new(); + for (path, script_list) in scripts { + trie.insert(path, script_list.clone()); + } + Some(trie) + } + None => None, + } +} + +/// Eval execution scripts. +#[cfg(feature = "chrome")] +pub async fn eval_execution_scripts( + page: &chromiumoxide::Page, + target_url: &str, + execution_scripts: &Option, +) { + if let Some(scripts) = &execution_scripts { + if let Some(script) = scripts.search(target_url) { + let _ = page.evaluate(script.as_str()).await; + } else if scripts.match_all { + if let Some(script) = scripts.root.value.as_ref() { + let _ = page.evaluate(script.as_str()).await; + } + } + } +} + +/// Run automation scripts. +#[cfg(feature = "chrome")] +pub async fn eval_automation_scripts( + page: &chromiumoxide::Page, + target_url: &str, + automation_scripts: &Option, +) { + if let Some(script_map) = automation_scripts { + if let Some(scripts) = script_map.search(target_url) { + let mut valid = false; + + for script in scripts { + if script == &WebAutomation::ValidateChain && !valid { + break; + } + match tokio::time::timeout(tokio::time::Duration::from_secs(60), script.run(page)) + .await + { + Ok(next) => valid = next, + Err(elasped) => { + log::warn!("Script execution timed out for: {target_url} - {elasped}") + } + } + } + } else if script_map.match_all { + if let Some(scripts) = script_map.root.value.as_ref() { + let mut valid = false; + + for script in scripts { + if script == &WebAutomation::ValidateChain && !valid { + break; + } + match tokio::time::timeout( + tokio::time::Duration::from_secs(60), + script.run(page), + ) + .await + { + Ok(next) => valid = next, + Err(elasped) => { + log::warn!("Script execution timed out for: {target_url} - {elasped}") + } + } + } + } + } + } +} + +#[cfg(feature = "chrome")] +/// default observer +pub const OBSERVER_SYMBOL_KEY: &str = "obs:3f2d6c07-5c01-4e2e-9ab0-8a8b9a7b2f4c"; +/// Installs a MutationObserver + URL change hooks and a drain function. +#[cfg(feature = "chrome")] +pub const INSTALL_OBSERVER_JS: &str = r##"(function(){const K=Symbol.for("obs:3f2d6c07-5c01-4e2e-9ab0-8a8b9a7b2f4c");if(globalThis[K])return globalThis[K];const Q=[],cap=100,cssPath=e=>{if(!e||1!==e.nodeType)return "#text";const t=[];for(;e&&1===e.nodeType&&e!==document.documentElement;){let n=e.nodeName.toLowerCase();if(e.id){n+="#"+e.id,t.unshift(n);break}let r=e,o=1;for(;(r=r.previousElementSibling)!=null;)if(r.nodeName===e.nodeName)o++;t.unshift(n+":nth-of-type("+o+")"),e=e.parentElement}return t.length?t.join(">"):"html"},state=()=>({url:location.href,title:document.title,viewport:{w:innerWidth,h:innerHeight,dpr:devicePixelRatio||1},scroll:{x:pageXOffset||0,y:pageYOffset||0}}),serialize=m=>"childList"===m.type?{type:"childList",target:cssPath(m.target),added:Array.from(m.addedNodes||[]).slice(0,3).map(n=>1===n.nodeType?n.outerHTML:"#text"),removed:Array.from(m.removedNodes||[]).slice(0,3).map(n=>1===n.nodeType?n.outerHTML:"#text" )}:"attributes"===m.type?{type:"attributes",target:cssPath(m.target),attribute:m.attributeName||"",old:m.oldValue??null,new:m.attributeName?m.target.getAttribute(m.attributeName):null}:{type:m.type,target:cssPath(m.target)},push=e=>{Q.push({t:Date.now(),state:state(),mutations:e?e.map(serialize):[]}),Q.length>cap&&Q.splice(0,Q.length-cap)};new MutationObserver(l=>push(Array.from(l))).observe(document.documentElement,{subtree:!0,childList:!0,attributes:!0,characterData:!1,attributeOldValue:!0});const notify=()=>push(null);["pushState","replaceState"].forEach(k=>{const o=history[k];history[k]=function(){const r=o.apply(this,arguments);return dispatchEvent(new Event("locationchange")),r}});addEventListener("popstate",()=>dispatchEvent(new Event("locationchange")));addEventListener("hashchange",()=>dispatchEvent(new Event("locationchange")));addEventListener("locationchange",notify,{passive:!0});const emitVal=el=>{const now=el&&null!=el.value?""+el.value:null,old=Object.prototype.hasOwnProperty.call(el,"__oldVal")?el.__oldVal:null;el.__oldVal=now,Q.push({t:Date.now(),state:state(),action:"input",target:cssPath(el),old:old,new:now}),Q.length>cap&&Q.splice(0,Q.length-cap)};addEventListener("input",e=>{const t=e.target;if(t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName))emitVal(t)},{capture:!0,passive:!0});addEventListener("change",e=>{const t=e.target;if(t&&("INPUT"===t.tagName||"TEXTAREA"===t.tagName))emitVal(t)},{capture:!0});addEventListener("click",e=>{const el=e.target instanceof Element?e.target:null;Q.push({t:Date.now(),state:state(),action:"click",target:el?cssPath(el):null,meta:el?{tag:el.tagName,id:el.id||null,class:el.className||null,href:el.getAttribute("href")||null,text:(el.textContent||"").trim().slice(0,120)}:null}),Q.length>cap&&Q.splice(0,Q.length-cap)},{capture:!0});const handle={drain:function(){const out=Q.slice();return Q.length=0,out}};return globalThis[K]=handle})()"##; + +/// Drains the recorded changes as an array; returns [] if not installed. +#[cfg(feature = "chrome")] +pub const DRAIN_CHANGES_JS: &str = r##"(function(){const h=globalThis[Symbol.for("obs:3f2d6c07-5c01-4e2e-9ab0-8a8b9a7b2f4c")];return h&&h.drain?h.drain():[]})()"##; + +#[cfg(feature = "chrome")] +/// Generate a random Symbol key for this session/run. +fn gen_symbol_key() -> String { + const ALPH: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let mut s = String::with_capacity(24); + for _ in 0..24 { + let i = fastrand::usize(..ALPH.len()); + s.push(ALPH[i] as char); + } + format!("obs:{}", s) +} + +#[cfg(feature = "chrome")] +/// Run automation scripts with state tracking. +pub async fn eval_automation_scripts_tracking( + page: &chromiumoxide::Page, + target_url: &str, + automation_scripts: &Option, + automation_data: &mut Vec, +) { + use base64::{engine::general_purpose::STANDARD, Engine}; + use chromiumoxide::cdp::js_protocol::runtime::EvaluateParams; + + fn eval_expr(expr: &str) -> EvaluateParams { + EvaluateParams::builder() + .expression(expr) + .eval_as_function_fallback(true) + .build() + .expect("eval_expr build failed") + } + + fn eval_value(expr: &str) -> EvaluateParams { + EvaluateParams::builder() + .expression(expr) + .return_by_value(true) + .build() + .expect("eval_value build failed") + } + + let symbol = gen_symbol_key(); + + let install = INSTALL_OBSERVER_JS.replacen(OBSERVER_SYMBOL_KEY, &symbol, 1); + let drain = DRAIN_CHANGES_JS.replacen(OBSERVER_SYMBOL_KEY, &symbol, 1); + + let _ = page.evaluate(eval_expr(&install)).await; + let _ = page.evaluate(eval_value(&drain)).await; + + /// The generic screenshot params. + fn ss_params() -> chromiumoxide::page::ScreenshotParams { + chromiumoxide::page::ScreenshotParams::builder() + .format(chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat::Png) + .full_page(true) + .quality(45) + .omit_background(false) + .build() + } + + // Helper to drain recorded changes as a single JSON string + async fn drain_changes_json( + page: &chromiumoxide::Page, + drain_eval: &str, + ) -> Option { + if let Ok(eval) = page.evaluate(eval_value(drain_eval)).await { + eval.value().cloned() + } else { + None + } + } + + let mut run_scripts: Option<&Vec> = None; + + if let Some(script_map) = automation_scripts { + if let Some(scripts) = script_map.search(target_url) { + run_scripts = Some(scripts); + } else if script_map.match_all { + if let Some(scripts) = script_map.root.value.as_ref() { + run_scripts = Some(scripts); + } + } + } + + if let Some(scripts) = run_scripts { + let mut valid = false; + + for (idx, script) in scripts.iter().enumerate() { + if script == &WebAutomation::ValidateChain && !valid { + break; + } + + let step_name = format!("step_{}_{}", idx + 1, script.label()); + + let step_result = + tokio::time::timeout(tokio::time::Duration::from_secs(60), script.run(page)).await; + + let mut automation_results = crate::page::AutomationResults::default(); + automation_results.input = format!("automation:{} url:{}", step_name, target_url); + + match step_result { + Ok(next) => { + valid = next; + if let Some(changes_json) = drain_changes_json(page, &drain).await { + automation_results.content_output = changes_json; + } + if let Ok(ss) = page.screenshot(ss_params()).await { + automation_results.screenshot_output = Some(STANDARD.encode(&ss)); + } + } + Err(elapsed) => { + automation_results.error = Some(format!( + "Script execution timed out for: {} - {}", + target_url, elapsed + )); + if let Some(changes_json) = drain_changes_json(page, &drain).await { + automation_results.content_output = changes_json; + } + if let Ok(ss) = page.screenshot(ss_params()).await { + automation_results.screenshot_output = Some(STANDARD.encode(&ss)); + } + log::warn!("Script execution timed out for: {target_url} - {elapsed}"); + } + } + + automation_data.push(automation_results); + } + } +} diff --git a/spider/src/features/chrome_viewport.rs b/spider/src/features/chrome_viewport.rs new file mode 100644 index 000000000..b8689871c --- /dev/null +++ b/spider/src/features/chrome_viewport.rs @@ -0,0 +1,82 @@ +use crate::features::chrome_common::Viewport; +use rand::distr::{weighted::WeightedIndex, Distribution}; +use rand::prelude::IndexedRandom; +use rand::rngs::ThreadRng; +use rand::Rng; + +/// Represents different types of devices for viewport simulation. +/// +/// The `DeviceType` enum categorizes devices into three main types: +/// `Mobile`, `Tablet`, and `Desktop`. These categories are typically used +/// to simulate and test varying screen resolutions and viewports in +/// web development, ensuring that web applications render correctly +/// across different platforms. +#[derive(Default, Clone, Copy, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum DeviceType { + /// Represents a mobile device. + /// + /// Mobile devices generally have smaller screens, and their + /// viewport sizes range from small to medium dimensions, typically + /// used for smartphones. + #[cfg_attr(feature = "serde", serde(rename = "mobile"))] + Mobile, + /// Represents a tablet device. + /// + /// Tablet devices have medium-sized screens, which are larger than + /// those of mobile devices but smaller than desktop monitors. + /// Viewport dimensions for tablets are typically larger than mobiles + /// and intended for medium-scale interfaces. + #[cfg_attr(feature = "serde", serde(rename = "tablet"))] + Tablet, + #[default] + /// Represents a desktop device. + /// + /// Desktop devices have larger screens compared to mobile and + /// tablet devices. Viewport dimensions for desktops are typically + /// the largest among the three, intended for full-scale applications + /// on monitors. + #[cfg_attr(feature = "serde", serde(rename = "desktop"))] + Desktop, +} + +/// Randomize viewport dimensions based on device type +pub fn randomize_viewport(device: &DeviceType) -> Viewport { + let mut rng = rand::rng(); + + randomize_viewport_rng(device, &mut rng) +} + +/// Randomize viewport dimensions based on device type. +pub fn randomize_viewport_rng(device: &DeviceType, rng: &mut ThreadRng) -> Viewport { + match device { + DeviceType::Mobile => { + let width = rng.random_range(320..=480); + let height = rng.random_range(480..=800); + Viewport::new(width, height) + } + DeviceType::Tablet => { + let width = rng.random_range(600..=800); + let height = rng.random_range(800..=1200); + Viewport::new(width, height) + } + DeviceType::Desktop => { + let width = rng.random_range(1024..=1920); + let height = rng.random_range(768..=1080); + Viewport::new(width, height) + } + } +} +/// Get a random viewport by selecting a random device type first. The weights are aligned in favor of desktop. +pub fn get_random_viewport() -> Viewport { + let mut rng = rand::rng(); + let device_types = [DeviceType::Mobile, DeviceType::Tablet, DeviceType::Desktop]; + + randomize_viewport(if let Ok(dist) = WeightedIndex::new([1, 1, 3]) { + &device_types[dist.sample(&mut rng)] + } else { + device_types + .choose(&mut rng) + .unwrap_or(&DeviceType::Desktop) + }) +} diff --git a/spider/src/features/decentralized_headers.rs b/spider/src/features/decentralized_headers.rs new file mode 100644 index 000000000..2d98ae82b --- /dev/null +++ b/spider/src/features/decentralized_headers.rs @@ -0,0 +1,241 @@ +use itertools::{Either, Itertools}; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::iter::FromIterator; +use std::str::FromStr; + +/// The prefix of all header values retrieved & proxied by a worker +pub const WORKER_PROXY_HEADER_PREFIX: &str = "zz-spider-r--"; + +/// The header name for a retrieved status code +pub const STATUS_CODE_HEADER_FIELD: HeaderName = HeaderName::from_static("status-code"); + +/// Shortcut for a proxied status-code. +const PROXIED_ORIGINAL_STATUS: HeaderName = HeaderName::from_static(const_format::concatcp!( + WORKER_PROXY_HEADER_PREFIX, + "status-code" +)); + +/// A helper function for adding the [WORKER_PROXY_HEADER_PREFIX] prefix to [name]. +fn set_prefix(name: impl AsRef) -> HeaderName { + let key_value = name.as_ref(); + let mut new_value = String::with_capacity(WORKER_PROXY_HEADER_PREFIX.len() + key_value.len()); + new_value.push_str(WORKER_PROXY_HEADER_PREFIX); + new_value.push_str(key_value); + + // Check not necessary, we are valid in 100% of the time. + match HeaderName::try_from(new_value) { + Ok(h) => h, + _ => HeaderName::from_static(""), + } +} + +/// A helper function to strip [WORKER_PROXY_HEADER_PREFIX] of the [name]. +/// Returns None if there is no prefix. +pub fn strip_prefix(name: impl AsRef) -> Option { + name.as_ref().strip_prefix(WORKER_PROXY_HEADER_PREFIX).map( + |stripped| match HeaderName::from_str(stripped) { + Ok(s) => s, + _ => HeaderName::from_static(""), + }, + ) +} + +/// A helper function to check if [name] starts with a [WORKER_PROXY_HEADER_PREFIX]. +pub fn has_prefix(name: impl AsRef) -> bool { + name.as_ref().starts_with(WORKER_PROXY_HEADER_PREFIX) +} + +/// A primitive builder for a proxied header. +/// All registered entries are prefixed by +/// [WORKER_PROXY_HEADER_PREFIX] +pub struct WorkerProxyHeaderBuilder { + headers: Vec<(HeaderName, T)>, + status_code: Option, +} + +impl Default for WorkerProxyHeaderBuilder { + fn default() -> Self { + Self::new() + } +} + +impl WorkerProxyHeaderBuilder { + /// Creates a new builder for a proxied header of a worker. + pub fn new() -> Self { + Self { + headers: Vec::new(), + status_code: None, + } + } +} + +impl WorkerProxyHeaderBuilder { + /// Initializes the builder with a specific size. + pub fn with_capacity(capacity: usize) -> Self { + WorkerProxyHeaderBuilder { + headers: Vec::with_capacity(capacity), + status_code: None, + } + } + + /// Adds an entry with ([key], [value]) to the builder. + pub fn insert>(&mut self, key: HeaderName, value: V) { + self.headers.push((key, value.into())) + } + + /// Adds an [entry] to the builder. + pub fn push(&mut self, entry: (HeaderName, T)) { + self.headers.push(entry) + } + + /// Sets the [status_code] to be stored in the proxied header. + /// Returns the old value if one exists. + pub fn set_status_code>(&mut self, status_code: S) -> Option { + self.status_code.replace(status_code.into()) + } + + /// Writes the content of self into [target]. The + pub fn write_to(self, target: &mut HeaderMap) { + for (k, v) in self.headers { + target.insert(set_prefix(&k), v); + } + if let Some(status_code) = self.status_code { + target.insert(PROXIED_ORIGINAL_STATUS, status_code); + } + } + + /// Builds a [HeaderMap] from the registered entries. + pub fn build(self) -> HeaderMap { + let mut new_map = HeaderMap::with_capacity(self.headers.len()); + self.write_to(&mut new_map); + new_map + } +} + +impl Extend<(Option, T)> for WorkerProxyHeaderBuilder { + fn extend, T)>>(&mut self, iter: I) { + for value in iter.into_iter() { + self.headers.push(( + value.0.expect("expected a header name, but got None"), + value.1, + )) + } + } +} + +/// Prefixes all entries in [headers] with [WORKER_PROXY_HEADER_PREFIX]. +/// Returns a new [HeaderMap] +pub fn as_proxy_headers(headers: HeaderMap) -> HeaderMap { + let mut new_headers = HeaderMap::with_capacity(0); + extend_with_proxy_headers(&mut new_headers, headers); + new_headers +} + +/// Extends the [target] with all values from [original_header_source], where all inserted +/// entries are prefixed with [WORKER_PROXY_HEADER_PREFIX]. +pub fn extend_with_proxy_headers, T)>>( + target: &mut HeaderMap, + proxied_headers: I, +) { + target.extend( + proxied_headers + .into_iter() + .map(|(key, value)| (key.map(|key_value| set_prefix(&key_value)), value)), + ) +} + +/// A splitted [HeaderMap], containing the entries for the original request and +pub struct HeaderSplit { + /// + pub original: HashMap, + /// Is none if there are no + pub proxy: HashMap, +} + +/// Splits the [header] in original and proxy. The proxy element keys are stripped from [WORKER_PROXY_HEADER_PREFIX]. +pub fn split_proxy_headers>( + header: I, +) -> HeaderSplit { + let (a, b) = header.into_iter().partition_map(|(k, v)| { + if let Some(stripped) = strip_prefix(&k) { + Either::Right((stripped, v)) + } else { + Either::Left((k, v)) + } + }); + HeaderSplit { + original: a, + proxy: b, + } +} + +/// Returns true if the [headers] at least contain one element with a key that +/// has [WORKER_PROXY_HEADER_PREFIX] as prefix. +pub fn has_proxy_entries(headers: &HeaderMap) -> bool { + headers.iter().any(|(k, _)| has_prefix(k)) +} + +/// Returns a new [HeaderMap], containing only the entries from [src] that have the [WORKER_PROXY_HEADER_PREFIX]. +/// The keys in the returned map are striped from [WORKER_PROXY_HEADER_PREFIX]. +pub fn extract_proxy_headers(src: &HeaderMap) -> HeaderMap { + HeaderMap::from_iter( + src.into_iter() + .filter_map(|(key, value)| Some((strip_prefix(key)?, value.clone()))), + ) +} + +#[cfg(test)] +mod tests { + use super::{ + extract_proxy_headers, set_prefix, WorkerProxyHeaderBuilder, PROXIED_ORIGINAL_STATUS, + STATUS_CODE_HEADER_FIELD, + }; + use reqwest::header::HeaderValue; + + #[test] + fn can_build_a_map() { + let mut builder = WorkerProxyHeaderBuilder::new(); + builder.insert( + reqwest::header::USER_AGENT, + HeaderValue::from_str("value").unwrap(), + ); + + builder.set_status_code(404); + let map = builder.build(); + + assert_eq!( + map.get(set_prefix(&reqwest::header::USER_AGENT)).unwrap(), + HeaderValue::from_str("value").unwrap() + ); + + assert_eq!( + map.get(PROXIED_ORIGINAL_STATUS).unwrap(), + HeaderValue::from(404) + ); + } + + #[test] + fn can_build_and_clean_a_map() { + let mut builder = WorkerProxyHeaderBuilder::new(); + builder.insert( + reqwest::header::USER_AGENT, + HeaderValue::from_str("value").unwrap(), + ); + + builder.set_status_code(404); + let map = builder.build(); + let cleaned = extract_proxy_headers(&map); + + assert_eq!( + cleaned.get(reqwest::header::USER_AGENT).unwrap(), + HeaderValue::from_str("value").unwrap() + ); + + assert_eq!( + cleaned.get(STATUS_CODE_HEADER_FIELD).unwrap(), + HeaderValue::from(404) + ); + } +} diff --git a/spider/src/features/disk.rs b/spider/src/features/disk.rs new file mode 100644 index 000000000..4e93a9123 --- /dev/null +++ b/spider/src/features/disk.rs @@ -0,0 +1,471 @@ +#[cfg(feature = "disk")] +use case_insensitive_string::CaseInsensitiveString; +#[cfg(feature = "disk")] +use hashbrown::HashSet; +#[cfg(feature = "disk")] +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(feature = "disk")] +use crate::utils::emit_log; +#[cfg(feature = "disk")] +use sqlx::{sqlite::SqlitePool, Sqlite, Transaction}; + +#[cfg(feature = "disk")] +lazy_static! { + static ref AC: aho_corasick::AhoCorasick = { + let patterns = vec![".", "/", ":", "\\", "?", "*", "\"", "<", ">", "|"]; + aho_corasick::AhoCorasick::new(&patterns).expect("valid replacer") + }; + static ref AC_REPLACE: [&'static str; 10] = ["_", "_", "_", "_", "_", "_", "_", "_", "_", "_"]; +} + +#[derive(Default, Debug, Clone)] +#[cfg(feature = "disk")] +/// Manage Sqlite database operations +pub struct DatabaseHandler { + /// Persist after drop. + pub persist: bool, + /// The crawl ID. + pub crawl_id: Option, + /// The connection pool. + pool: tokio::sync::OnceCell, + /// Initial seed ran. + pub seeded: bool, +} + +#[derive(Default, Debug, Clone)] +#[cfg(not(feature = "disk"))] +/// Manage Sqlite database operations +pub struct DatabaseHandler { + /// Persist after drop. + pub persist: bool, +} + +#[cfg(not(feature = "disk"))] +impl DatabaseHandler { + /// A new DB handler. + pub fn new(_crawl_id: &Option) -> Self { + Default::default() + } + /// Delete the db by id. + pub fn delete_db_by_id(&mut self) {} +} + +#[cfg(feature = "disk")] +impl DatabaseHandler { + /// A new DB handler. + pub fn new(crawl_id: &Option) -> Self { + Self { + persist: false, + pool: tokio::sync::OnceCell::const_new(), + crawl_id: match crawl_id { + Some(id) => { + let sanitized_id = AC.replace_all(id, &*AC_REPLACE); + + Some(format!("{}_{}", sanitized_id, get_id())) + } + _ => None, + }, + seeded: false, + } + } + + /// Determine if the pool is initialized. + pub fn pool_inited(&self) -> bool { + self.pool.initialized() + } + + /// Determine if a seed was already done. + pub fn ready(&self) -> bool { + self.seeded + } + + /// Set the seeded state + pub fn set_seeded(&mut self, seeded: bool) { + self.seeded = seeded; + } + + /// Set the persist state + pub fn set_persisted(&mut self, persist: bool) { + self.persist = persist; + } + + /// Generate a sqlite pool. + pub async fn generate_pool(&self) -> SqlitePool { + let db_path = get_db_path(&self.crawl_id); + let direct = db_path.starts_with("sqlite://"); + + // not a shared sqlite db. + if direct { + create_file_and_directory(&db_path[9..]).await; + } else { + create_file_and_directory(&db_path).await; + } + + let db_url = if direct { + db_path + } else { + format!("sqlite://{}", db_path) + }; + + let pool = SqlitePool::connect_lazy(&db_url).expect("Failed to connect to the database"); + + let create_resources_table = sqlx::query( + r#"CREATE TABLE IF NOT EXISTS resources ( + id INTEGER PRIMARY KEY, + url TEXT NOT NULL COLLATE NOCASE + ); + CREATE INDEX IF NOT EXISTS idx_url ON resources (url COLLATE NOCASE);"#, + ) + .execute(&pool); + + let create_signatures_table = sqlx::query( + r#"CREATE TABLE IF NOT EXISTS signatures ( + id INTEGER PRIMARY KEY, + url INTEGER NOT NULL + ); + CREATE INDEX IF NOT EXISTS idx_url ON signatures (url);"#, + ) + .execute(&pool); + + // Run the queries concurrently + let (resources_result, signatures_result) = + tokio::join!(create_resources_table, create_signatures_table); + + // Handle possible errors + if let Err(e) = resources_result { + log::warn!("SQLite error creating resources table: {:?}", e); + } + + if let Err(e) = signatures_result { + log::warn!("SQLite error creating signatures table: {:?}", e); + } + + pool + } + + /// Get or initialize the database pool + pub async fn initlaize_pool(&self) { + if !self.pool_inited() { + let _ = self.pool.set(self.generate_pool().await); + } + } + + /// Set the pool directly. + pub async fn set_pool(&self, pool: SqlitePool) { + let _ = self.pool.set(pool); + } + + /// Get or initialize the database pool + pub async fn get_db_pool(&self) -> &SqlitePool { + self.pool.get_or_init(|| self.generate_pool()).await + } + + /// Check if a URL exists (ignore case) + pub async fn url_exists(&self, pool: &SqlitePool, url_to_check: &str) -> bool { + match sqlx::query("SELECT 1 FROM resources WHERE url = ? LIMIT 1") + .bind(url_to_check) + .fetch_optional(pool) + .await + { + Ok(result) => result.is_some(), + Err(e) => { + if let Some(db_err) = e.as_database_error() { + emit_log(db_err.message()); + } else { + emit_log(&format!("A non-database error occurred: {:?}", e)); + } + false + } + } + } + + /// Check if a signature exists (ignore case) + pub async fn signature_exists(&self, pool: &SqlitePool, signature_to_check: u64) -> bool { + match sqlx::query("SELECT 1 FROM signatures WHERE url = ? LIMIT 1") + .bind(signature_to_check.to_string()) + .fetch_optional(pool) + .await + { + Ok(result) => result.is_some(), + Err(e) => { + if let Some(db_err) = e.as_database_error() { + emit_log(db_err.message()); + } else { + emit_log(&format!("A non-database error occurred: {:?}", e)); + } + false + } + } + } + + /// Insert a new URL if it doesn't exist + pub async fn insert_url(&self, pool: &SqlitePool, new_url: &str) { + if !self.url_exists(pool, new_url).await { + if let Err(e) = sqlx::query("INSERT INTO resources (url) VALUES (?)") + .bind(new_url) + .execute(pool) + .await + { + if let Some(db_err) = e.as_database_error() { + emit_log(db_err.message()); + } else { + emit_log(&format!("A non-database error occurred: {:?}", e)); + } + } + } + } + + /// Insert a new signature if it doesn't exist + pub async fn insert_signature(&self, pool: &SqlitePool, new_signature: u64) { + if !self.signature_exists(pool, new_signature).await { + if let Err(e) = sqlx::query("INSERT INTO signatures (url) VALUES (?)") + .bind(new_signature.to_string()) + .execute(pool) + .await + { + if let Some(db_err) = e.as_database_error() { + emit_log(db_err.message()); + } else { + emit_log(&format!("A non-database error occurred: {:?}", e)); + } + } + } + } + + /// Seed the database and manage URLs + pub async fn seed( + &self, + pool: &SqlitePool, + mut urls: HashSet, + ) -> Result, sqlx::Error> { + const CHUNK_SIZE: usize = 500; + const KEEP_COUNT: usize = 100; + + let mut tx: Transaction<'_, Sqlite> = pool.begin().await?; + let mut keep_urls = HashSet::with_capacity(KEEP_COUNT); + + for url in urls.iter().take(KEEP_COUNT) { + keep_urls.insert(url.clone()); + } + + urls.retain(|url| !keep_urls.contains(url)); + + for chunk in keep_urls.iter().collect::>().chunks(CHUNK_SIZE) { + let mut query = "INSERT OR IGNORE INTO resources (url) VALUES ".to_string(); + query.push_str(&vec!["(?)"; chunk.len()].join(", ")); + let mut statement = sqlx::query(&query); + + for url in chunk { + statement = statement.bind(url.to_string()); + } + + statement.execute(&mut *tx).await?; + } + + for chunk in urls.drain().collect::>().chunks(CHUNK_SIZE) { + let mut query = "INSERT OR IGNORE INTO resources (url) VALUES ".to_string(); + query.push_str(&vec!["(?)"; chunk.len()].join(", ")); + let mut statement = sqlx::query(&query); + + for url in chunk { + statement = statement.bind(url.to_string()); + } + + statement.execute(&mut *tx).await?; + } + + tx.commit().await?; + + Ok(keep_urls) + } + + /// Count the records stored. + pub async fn count_records(pool: &SqlitePool) -> Result { + let result = sqlx::query_scalar::<_, u64>("SELECT COUNT(*) FROM resources") + .fetch_one(pool) + .await?; + Ok(result) + } + + /// Get all the resources stored. + pub async fn get_all_resources( + pool: &SqlitePool, + ) -> Result, sqlx::Error> { + use sqlx::Row; + let rows = sqlx::query("SELECT url FROM resources") + .fetch_all(pool) // Fetches all rows at once. + .await?; + + let urls = rows + .into_iter() + .map(|row| row.get::("url").into()) + .collect(); + + Ok(urls) + } + + /// Clear DB by id + pub fn delete_db_by_id(&self) { + let _ = std::fs::remove_file(get_db_path(&self.crawl_id)); + } + + /// Clear the resources table. + pub async fn clear_table(pool: &SqlitePool) -> Result<(), sqlx::Error> { + let _ = tokio::join!( + sqlx::query("DELETE FROM resources").execute(pool), + sqlx::query("DELETE FROM signatures").execute(pool) + ); + Ok(()) + } +} + +#[cfg(feature = "disk")] +impl Drop for DatabaseHandler { + fn drop(&mut self) { + if !self.persist { + self.delete_db_by_id(); + } + } +} + +/// simple counter to get the next ID. +#[cfg(feature = "disk")] +fn get_id() -> usize { + static COUNTER: AtomicUsize = AtomicUsize::new(1); + + let mut current = COUNTER.load(Ordering::Relaxed); + loop { + let next = if current == usize::MAX { + 1 + } else { + current + 1 + }; + match COUNTER.compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed) { + Ok(_) => return current, + Err(updated) => current = updated, + } + } +} + +/// Get the db path. +pub fn get_db_path(crawl_id: &Option) -> String { + // Get the base database URL or default to a temporary directory + let base_url = std::env::var("SQLITE_DATABASE_URL").unwrap_or_else(|_| { + let temp_dir = std::env::temp_dir(); + temp_dir.to_string_lossy().into_owned() + }); + + let delim = if base_url.starts_with("sqlite://memory:") { + ":" + } else { + "/" + }; + + // Determine the db_path + let db_path = match crawl_id { + Some(crawl_id) => { + format!( + "{}{delim}spider_{}.db", + base_url.trim_end_matches('/'), + crawl_id.replace(".", "_") + ) + } + None => format!("{}{delim}spider.db", base_url.trim_end_matches('/')), + }; + + db_path +} + +/// Create the file and directory if locally. +#[cfg(feature = "disk")] +async fn create_file_and_directory(file_path: &str) { + let path = std::path::Path::new(file_path); + + if let Some(parent) = path.parent() { + let _ = crate::utils::uring_fs::create_dir_all(parent.display().to_string()).await; + } + + if !path.exists() { + let _ = crate::utils::uring_fs::write_file(path.display().to_string(), Vec::new()).await; + } +} + +#[cfg(test)] +#[cfg(feature = "disk")] +mod tests { + use super::*; + use tokio; + + #[tokio::test] + async fn test_connect_db() { + let handler = DatabaseHandler::new(&Some("example.com".into())); + let test_url = CaseInsensitiveString::new("http://example.com"); + let pool = handler.get_db_pool().await; + + if handler.url_exists(pool, &test_url).await { + println!("URL '{}' already exists in the database.", test_url); + } else { + handler.insert_url(pool, &test_url).await; + println!("URL '{}' was inserted into the database.", test_url); + } + + assert!( + handler.url_exists(pool, &test_url).await, + "URL should exist after insertion." + ); + } + + #[tokio::test] + async fn test_url_insert_and_exists() { + let handler = DatabaseHandler::new(&Some("example.com".into())); + let new_url = CaseInsensitiveString::new("http://new-example.com"); + let pool = handler.get_db_pool().await; + + assert!( + !handler.url_exists(pool, &new_url).await, + "URL should not exist initially." + ); + + handler.insert_url(pool, &new_url).await; + assert!( + handler.url_exists(pool, &new_url).await, + "URL should exist after insertion." + ); + } + + #[tokio::test] + async fn test_url_case_insensitivity() { + let handler = DatabaseHandler::new(&Some("case-test.com".into())); + let url1 = CaseInsensitiveString::new("http://case-test.com"); + let url2 = CaseInsensitiveString::new("http://CASE-TEST.com"); + let pool = handler.get_db_pool().await; + + handler.insert_url(pool, &url1).await; + assert!( + handler.url_exists(pool, &url2).await, + "URL check should be case-insensitive." + ); + } + + #[tokio::test] + async fn test_seed_urls() { + let handler = DatabaseHandler::new(&Some("example.com".into())); + let mut urls = HashSet::new(); + urls.insert(CaseInsensitiveString::new("http://foo.com")); + urls.insert(CaseInsensitiveString::new("http://bar.com")); + let pool = handler.get_db_pool().await; + + handler + .seed(pool, urls.clone()) + .await + .expect("Seeding failed"); + + for url in urls { + assert!( + handler.url_exists(pool, &url).await, + "Seeded URL should exist after seeding." + ); + } + } +} diff --git a/spider/src/features/gemini.rs b/spider/src/features/gemini.rs new file mode 100644 index 000000000..c7c2f9fae --- /dev/null +++ b/spider/src/features/gemini.rs @@ -0,0 +1,52 @@ +//! Gemini-specific prompts and utilities + +use lazy_static::lazy_static; + +const PROMPT: &str = r#"task-js-snippet-web-int. +HTML, URL, user-prompt-action -> provide pure js. +Exec-in-browser, no extra fmt/annot. +Only raw-js for function/applic. +Ex: window.location.href='https://www.google.com/search?q=Movies';"#; + +const PROMPT_EXTRA: &str = r#"Provide a JSON response, e.g., {"content": ["Something"], "js": "window.location.href = 'https://www.google.com/search?q=Movies';"}. Use this structure. If no JS is needed, set "js" to ""."#; + +lazy_static! { + /// The base system prompt for driving the browser. + pub static ref BROWSER_ACTIONS_SYSTEM_PROMPT: String = PROMPT.trim().to_string(); + /// The base system prompt for extra data. + pub static ref BROWSER_ACTIONS_SYSTEM_EXTRA_PROMPT: String = PROMPT_EXTRA.trim().to_string(); +} + +/// Estimate token count for Gemini (rough approximation). +/// Gemini uses a different tokenizer, but ~4 chars per token is a reasonable estimate. +pub fn estimate_token_count(text: &str) -> usize { + text.len() / 4 +} + +/// Calculate max tokens available for response given input size. +pub fn calculate_max_tokens( + model_name: &str, + max_tokens: u16, + resource: &str, + prompt: &str, +) -> usize { + // Gemini model context windows (approximate) + let model_max: usize = if model_name.contains("1.5-pro") + || model_name.contains("2.0") + || model_name.contains("2.5") + || model_name.contains("1.5-flash") + || model_name.contains("flash") + { + 1_000_000 // 1M context for Gemini 1.5 Pro, 2.0+, and flash models + } else { + 32_000 // Default conservative estimate + }; + + let system_tokens = estimate_token_count(&BROWSER_ACTIONS_SYSTEM_PROMPT); + let resource_tokens = estimate_token_count(resource); + let prompt_tokens = estimate_token_count(prompt); + let input_tokens = system_tokens + resource_tokens + prompt_tokens; + + let available = model_max.saturating_sub(input_tokens); + available.min(max_tokens as usize) +} diff --git a/spider/src/features/gemini_common.rs b/spider/src/features/gemini_common.rs new file mode 100644 index 000000000..0db239807 --- /dev/null +++ b/spider/src/features/gemini_common.rs @@ -0,0 +1,185 @@ +//! Common Gemini types and configurations + +use crate::features::openai_common::{Prompt, ResponseFormatJsonSchema}; + +/// Default Gemini model - uses the latest flash model shortcut +pub const DEFAULT_GEMINI_MODEL: &str = "gemini-flash-latest"; + +/// The Gemini configs to use for dynamic Javascript execution and other functionality. +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + all( + not(feature = "regex"), + not(feature = "openai"), + not(feature = "cache_openai"), + not(feature = "gemini"), + not(feature = "cache_gemini") + ), + derive(PartialEq) +)] +pub struct GeminiConfigs { + /// The prompt to use for the Chat. Example: Search for movies. This will attempt to get the code required to perform the action on the page. + pub prompt: Prompt, + #[cfg_attr(feature = "serde", serde(default))] + /// The model to use. Example: gemini-flash-latest, gemini-2.0-flash, gemini-1.5-pro + pub model: String, + #[cfg_attr(feature = "serde", serde(default))] + /// The max tokens to use for the request (max_output_tokens in Gemini). + pub max_tokens: u16, + #[cfg_attr(feature = "serde", serde(default))] + /// The temperature between 0 - 2. + pub temperature: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// The top_p for the request. + pub top_p: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// The top_k for the request (Gemini-specific). + pub top_k: Option, + /// Prompts to use for certain urls. If this is set only the urls that match exactly are ran. + pub prompt_url_map: + Option>>>, + #[cfg_attr(feature = "serde", serde(default))] + /// Extra data, this will merge the prompts and try to get the content for you. Example: extracting data from the page. + pub extra_ai_data: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// Map to paths. If the prompt_url_map has a key called /blog and all blog pages are found like /blog/something the same prompt is performed unless an exact match is found. + pub paths_map: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// Take a screenshot of the page after each JS script execution. The screenshot is stored as a base64. + pub screenshot: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// The API key to use for the request. + pub api_key: Option, + #[cfg_attr( + feature = "serde", + serde(default), + serde(skip_serializing, skip_deserializing) + )] + /// Use caching to cache the prompt. This does nothing without the 'cache_gemini' flag enabled. + pub cache: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// Use structured JSON mode. + pub json_schema: Option, +} + +/// The usage used from Gemini. +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GeminiUsage { + /// The prompt tokens used. + pub prompt_tokens: u32, + /// The completion tokens used. + pub completion_tokens: u32, + /// The total tokens used. + pub total_tokens: u32, + /// Is the request cached? Useful for ignoring the tokens. + pub cached: bool, +} + +/// The results from Gemini. +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GeminiReturn { + /// The response of the AI. + pub response: String, + /// The usage of the request. + pub usage: GeminiUsage, + /// The error of the request if any. + pub error: Option, +} + +#[cfg(feature = "cache_gemini")] +/// The Gemini cache to use. +pub type GeminiCache = moka::future::Cache; + +#[cfg(not(feature = "cache_gemini"))] +/// The Gemini cache to use. +pub type GeminiCache = String; + +impl GeminiConfigs { + /// GeminiConfigs for Gemini chrome dynamic scripting. + pub fn new(model: &str, prompt: &str, max_tokens: u16) -> GeminiConfigs { + Self { + model: model.into(), + prompt: Prompt::Single(prompt.into()), + max_tokens, + ..Default::default() + } + } + + /// GeminiConfigs with default model (gemini-flash-latest). + pub fn new_default(prompt: &str, max_tokens: u16) -> GeminiConfigs { + Self::new(DEFAULT_GEMINI_MODEL, prompt, max_tokens) + } + + /// GeminiConfigs for Gemini chrome dynamic scripting and caching. + pub fn new_cache( + model: &str, + prompt: &str, + max_tokens: u16, + cache: Option, + ) -> GeminiConfigs { + Self { + model: model.into(), + prompt: Prompt::Single(prompt.into()), + max_tokens, + cache, + ..Default::default() + } + } + + /// GeminiConfigs for Gemini chrome dynamic scripting multi chain prompts. + pub fn new_multi(model: &str, prompt: I, max_tokens: u16) -> GeminiConfigs + where + I: IntoIterator, + S: AsRef, + { + Self { + model: model.into(), + prompt: Prompt::Multi(prompt.into_iter().map(|s| s.as_ref().to_string()).collect()), + max_tokens, + ..Default::default() + } + } + + /// GeminiConfigs for Gemini chrome dynamic scripting multi chain prompts with prompt caching. The feature flag 'cache_gemini' is required. + pub fn new_multi_cache( + model: &str, + prompt: I, + max_tokens: u16, + cache: Option, + ) -> GeminiConfigs + where + I: IntoIterator, + S: AsRef, + { + Self { + model: model.into(), + prompt: Prompt::Multi(prompt.into_iter().map(|s| s.as_ref().to_string()).collect()), + max_tokens, + cache, + ..Default::default() + } + } + + /// Set extra AI data to return results. + pub fn set_extra(&mut self, extra_ai_data: bool) -> &mut Self { + self.extra_ai_data = extra_ai_data; + self + } + + /// Set the top_k parameter (Gemini-specific). + pub fn set_top_k(&mut self, top_k: Option) -> &mut Self { + self.top_k = top_k; + self + } +} + +#[test] +#[cfg(feature = "gemini")] +fn deserialize_gemini_configs() { + let gemini_configs_json = r#"{"prompt":"change background blue","model":"gemini-flash-latest","max_tokens":256,"temperature":0.54,"top_p":0.17}"#; + let configs = serde_json::from_str::(gemini_configs_json).ok(); + assert!(configs.is_some()) +} diff --git a/spider/src/features/glob.rs b/spider/src/features/glob.rs new file mode 100644 index 000000000..d9ab49a03 --- /dev/null +++ b/spider/src/features/glob.rs @@ -0,0 +1,273 @@ +use crate::CaseInsensitiveString; + +lazy_static! { + static ref RE: regex::Regex = { + unsafe { + regex::Regex::new( + r"(?x) + # list + (?\{(?[^}\\}^\{]+)}) | + # range + (?\[(?:(?(?0*)\d+|[a-z]))-(?:(?\d+|[a-z]))(?::(?\d+))?]) + ", + ) + .unwrap_unchecked() + } + }; +} + +/// expand a website url to a glob pattern set +pub fn expand_url(url: &str) -> Vec { + use itertools::Itertools; + + let mut matches = Vec::new(); + + for capture in RE.captures_iter(url) { + match ( + capture.name("list"), + capture.name("items"), + capture.name("range"), + capture.name("start"), + capture.name("end"), + ) { + // matches a list + (Some(list), Some(items), _, _, _) => { + let substring = list.as_str(); + + let items = items + .as_str() + .split(",") + .map(|item| (item.to_string(), substring)) + .collect::>(); + + matches.push(items); + } + // matches a range + (_, _, Some(range), Some(start), Some(end)) => { + let substring = range.as_str(); + let step = match capture.name("step") { + Some(step) => step.as_str().parse::().unwrap(), + None => 1, + }; + let start_str = start.as_str(); + let end_str = end.as_str(); + + let width = match capture.name("padding") { + Some(padding) => { + if !padding.as_str().is_empty() { + start_str.len() + } else { + 0 + } + } + None => 0, + }; + + match (start_str.parse::(), end_str.parse::()) { + // start and end are numbers + (Ok(s), Ok(e)) => { + let items = (s..e + 1) + .step_by(step) + .map(|num| { + ( + format!("{:0>width$}", num.to_string(), width = width), + substring, + ) + }) + .collect::>(); + + matches.push(items); + } + // start and end are characters + _ => { + let s = start_str.as_bytes()[0]; + let e = end_str.as_bytes()[0]; + let items = (s..e + 1) + .map(|char| (String::from_utf8_lossy(&[char]).to_string(), substring)) + .collect::>(); + + matches.push(items); + } + }; + } + _ => {} + } + } + + if matches.is_empty() { + return Vec::new(); + } + + matches + .iter() + .multi_cartesian_product() + .map(|combination| { + let mut new_url = CaseInsensitiveString::new(url); + + for (replacement, substring) in combination { + new_url = new_url + .inner() + .replace(substring, replacement.as_str()) + .into(); + } + + new_url + }) + .collect::>() +} + +#[test] +fn test_expand_url_list() { + let url = "https://choosealicense.com/licenses/{mit,apache-2.0,mpl-2.0}/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/mit/", + "https://choosealicense.com/licenses/apache-2.0/", + "https://choosealicense.com/licenses/mpl-2.0/" + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_list_escaped_closing() { + let url = "https://choosealicense.com/licenses/{mit\\}/"; + + assert_eq!(expand_url(url), Vec::::new()); +} + +#[test] +fn test_expand_url_numerical_range() { + let url = "https://choosealicense.com/licenses/bsd-[2-4]-clause/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/bsd-2-clause/", + "https://choosealicense.com/licenses/bsd-3-clause/", + "https://choosealicense.com/licenses/bsd-4-clause/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_numerical_range_singe_item() { + let url = "https://choosealicense.com/licenses/bsd-[4-4]-clause/"; + + assert_eq!( + expand_url(url), + ["https://choosealicense.com/licenses/bsd-4-clause/"] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_numerical_range_with_step() { + let url = "https://choosealicense.com/licenses/bsd-[2-4:2]-clause/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/bsd-2-clause/", + "https://choosealicense.com/licenses/bsd-4-clause/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_numerical_range_with_padding() { + let url = "https://choosealicense.com/licenses/bsd-[002-004]-clause/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/bsd-002-clause/", + "https://choosealicense.com/licenses/bsd-003-clause/", + "https://choosealicense.com/licenses/bsd-004-clause/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_numerical_range_with_padding_ignore_end_padding() { + let url = "https://choosealicense.com/licenses/bsd-[008-10]-clause/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/bsd-008-clause/", + "https://choosealicense.com/licenses/bsd-009-clause/", + "https://choosealicense.com/licenses/bsd-010-clause/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_alphabetical_range() { + let url = "https://choosealicense.com/licenses/[w-z]lib/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/wlib/", + "https://choosealicense.com/licenses/xlib/", + "https://choosealicense.com/licenses/ylib/", + "https://choosealicense.com/licenses/zlib/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_combination() { + let url = "https://choosealicense.com/licenses/bsd-[2-4]-clause{,-clear}/"; + + assert_eq!( + expand_url(url), + [ + "https://choosealicense.com/licenses/bsd-2-clause/", + "https://choosealicense.com/licenses/bsd-2-clause-clear/", + "https://choosealicense.com/licenses/bsd-3-clause/", + "https://choosealicense.com/licenses/bsd-3-clause-clear/", + "https://choosealicense.com/licenses/bsd-4-clause/", + "https://choosealicense.com/licenses/bsd-4-clause-clear/", + ] + .iter() + .map(CaseInsensitiveString::new) + .collect::>() + ); +} + +#[test] +fn test_expand_url_empty() { + let url = "https://choosealicense.com"; + + assert_eq!(expand_url(url), Vec::::new()); +} + +#[test] +fn test_expand_url_percent_encoded() { + let url = "https://choosealicense.com/licenses/%7Bmit%7D/"; + + assert_eq!(expand_url(url), Vec::::new()); +} diff --git a/spider/src/features/mod.rs b/spider/src/features/mod.rs new file mode 100644 index 000000000..ab7d00fed --- /dev/null +++ b/spider/src/features/mod.rs @@ -0,0 +1,58 @@ +/// Chrome utils +#[cfg(feature = "chrome")] +pub mod chrome; +#[cfg(feature = "chrome")] +/// Chrome launch args. +pub(crate) mod chrome_args; +/// Common modules for Chrome +pub mod chrome_common; +#[cfg(feature = "real_browser")] +/// Viewport +pub mod chrome_viewport; + +/// WebDriver utils +#[cfg(feature = "webdriver")] +pub mod webdriver; +#[cfg(feature = "webdriver")] +/// WebDriver launch args. +pub(crate) mod webdriver_args; +/// Common modules for WebDriver +pub mod webdriver_common; + +/// Decentralized header handling +#[cfg(feature = "decentralized_headers")] +pub mod decentralized_headers; +/// Disk options +pub mod disk; +/// URL globbing +#[cfg(feature = "glob")] +pub mod glob; +/// OpenAI +#[cfg(feature = "openai")] +pub mod openai; +/// Common modules for OpenAI +pub mod openai_common; + +/// Gemini +#[cfg(feature = "gemini")] +pub mod gemini; +/// Common modules for Gemini +pub mod gemini_common; + +/// Solve all. +pub mod solvers; + +#[cfg(all(not(feature = "simd"), feature = "openai"))] +pub(crate) use serde_json; +#[cfg(all(feature = "simd", feature = "openai"))] +pub(crate) use sonic_rs as serde_json; + +/// Automation scripts. +pub mod automation; + +/// Web search integration. +#[cfg(feature = "search")] +pub mod search; +/// Search provider implementations. +#[cfg(feature = "search")] +pub mod search_providers; diff --git a/spider/src/features/openai.rs b/spider/src/features/openai.rs new file mode 100644 index 000000000..7ab1a000c --- /dev/null +++ b/spider/src/features/openai.rs @@ -0,0 +1,81 @@ +use async_openai::types::chat::ChatCompletionRequestSystemMessageArgs; +use tiktoken_rs::{get_chat_completion_max_tokens, ChatCompletionRequestMessage}; + +const PROMPT: &str = r#"task-js-snippet-web-int.\n +HTML, URL, user-prompt-action -> provide pure js.\n +Exec-in-browser, no extra fmt/annot.\n +Only raw-js for function/applic.\n +Ex: window.location.href='https://www.google.com/search?q=Movies';"#; + +const PROMPT_EXTRA: &str = r#"Provide a JSON response, e.g., {"content": ["Something"], "js": "window.location.href = 'https://www.google.com/search?q=Movies';"}. Use this structure. If no JS is needed, set "js" to ""."#; + +lazy_static! { + /// The base system prompt for driving the browser. + pub static ref BROWSER_ACTIONS_SYSTEM_PROMPT: async_openai::types::chat::ChatCompletionRequestMessage = { + ChatCompletionRequestSystemMessageArgs::default() + .content(PROMPT.trim()) + .build() + .unwrap() + .into() + }; + /// The base system prompt for extra data. + pub static ref BROWSER_ACTIONS_SYSTEM_EXTRA_PROMPT: async_openai::types::chat::ChatCompletionRequestMessage = { + ChatCompletionRequestSystemMessageArgs::default() + .content(PROMPT_EXTRA.trim()) + .build() + .unwrap() + .into() + }; + /// The prompt completion for tiktoken token counting. + pub static ref BROWSER_ACTIONS_SYSTEM_PROMPT_COMPLETION: tiktoken_rs::ChatCompletionRequestMessage = { + tiktoken_rs::ChatCompletionRequestMessage { + content: Some(PROMPT.trim().to_string()), + role: "system".to_string(), + name: None, + function_call: None, + } + }; +} + +/// calculate the max tokens for a request +pub fn calculate_max_tokens( + model_name: &str, + max_tokens: u16, + base_chat_prompt: &ChatCompletionRequestMessage, + resource: &str, + prompt: &str, +) -> usize { + let messages = if prompt.is_empty() { + vec![ + base_chat_prompt.clone(), + ChatCompletionRequestMessage { + content: Some(resource.to_string()), + role: "assistant".to_string(), + name: None, + function_call: None, + }, + ] + } else { + vec![ + base_chat_prompt.clone(), + ChatCompletionRequestMessage { + content: Some(resource.to_string()), + role: "assistant".to_string(), + name: None, + function_call: None, + }, + ChatCompletionRequestMessage { + content: Some(prompt.to_string()), + role: "user".to_string(), + name: None, + function_call: None, + }, + ] + }; + + match get_chat_completion_max_tokens(model_name, &messages) { + Ok(r) => r, + _ => max_tokens.into(), + } + .min(max_tokens.into()) +} diff --git a/spider/src/features/openai_common.rs b/spider/src/features/openai_common.rs new file mode 100644 index 000000000..c862cb8fe --- /dev/null +++ b/spider/src/features/openai_common.rs @@ -0,0 +1,308 @@ +/// The type of prompt to use. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub enum Prompt { + /// A single prompt to run. + Single(String), + /// Multiple prompts to run after one another. + Multi(std::collections::VecDeque), +} + +impl std::fmt::Display for Prompt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Prompt::Single(s) => write!(f, "{}", s), + Prompt::Multi(m) => write!(f, "{:?}", m), + } + } +} + +#[cfg(feature = "openai")] +impl std::str::FromStr for Prompt { + type Err = super::serde_json::Error; + + fn from_str(s: &str) -> Result { + if let Ok(prompts) = super::serde_json::from_str::>(s) { + Ok(Prompt::Multi( + prompts + .into_iter() + .collect::>(), + )) + } else { + Ok(Prompt::Single(s.to_string())) + } + } +} + +impl Prompt { + /// A new single prompt. + pub fn new_single(prompt: &str) -> Self { + Prompt::Single(prompt.into()) + } + /// Multiple prompts to run after another. + pub fn new_multiple(prompt: std::collections::VecDeque) -> Self { + Prompt::Multi(prompt) + } + /// Get the next prompt + pub fn next(&mut self) -> Option { + match self { + Prompt::Single(prompt) => { + if prompt.is_empty() { + None + } else { + Some(prompt.drain(..).collect()) + } + } + Prompt::Multi(prompt) => prompt.pop_front(), + } + } +} + +impl Default for Prompt { + fn default() -> Self { + Prompt::Single(Default::default()) + } +} + +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + all( + not(feature = "regex"), + not(feature = "openai"), + not(feature = "cache_openai") + ), + derive(PartialEq) +)] +/// Structured data response format. +pub struct ResponseFormatJsonSchema { + /// A description of what the response format is for, used by the model to determine how to respond in the format. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub description: Option, + /// The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + pub name: String, + /// The schema for the response format, described as a JSON Schema object. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub schema: Option, + /// Whether to enable strict schema adherence when generating the output. If set to true, the model will always follow the exact schema defined in the `schema` field. Only a subset of JSON Schema is supported when `strict` is `true`. To learn more, read the [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub strict: Option, +} + +/// The GPT configs to use for dynamic Javascript execution and other functionality. +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + all( + not(feature = "regex"), + not(feature = "openai"), + not(feature = "cache_openai") + ), + derive(PartialEq) +)] +pub struct GPTConfigs { + /// The prompt to use for the Chat. Example: Search for movies. This will attempt to get the code required to perform the action on the page. + pub prompt: Prompt, + #[cfg_attr(feature = "serde", serde(default))] + /// The model to use. Example: gpt-4-1106-preview or gpt-3.5-turbo-16k + pub model: String, + #[cfg_attr(feature = "serde", serde(default))] + /// The max tokens to use for the request. + pub max_tokens: u16, + #[cfg_attr(feature = "serde", serde(default))] + /// The temperature between 0 - 2. + pub temperature: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// The user for the request. + pub user: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// The top priority for the request. + pub top_p: Option, + /// Prompts to use for certain urls. If this is set only the urls that match exactly are ran. + pub prompt_url_map: + Option>>>, + #[cfg_attr(feature = "serde", serde(default))] + /// Extra data, this will merge the prompts and try to get the content for you. Example: extracting data from the page. + pub extra_ai_data: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// Map to paths. If the prompt_url_map has a key called /blog and all blog pages are found like /blog/something the same prompt is perform unless an exact match is found. + pub paths_map: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// Take a screenshot of the page after each JS script execution. The screenshot is stored as a base64. + pub screenshot: bool, + #[cfg_attr(feature = "serde", serde(default))] + /// The API key to use for the request. + pub api_key: Option, + #[cfg_attr( + feature = "serde", + serde(default), + serde(skip_serializing, skip_deserializing) + )] + /// Use caching to cache the prompt. This does nothing without the 'cache_openai' flag enabled. + pub cache: Option, + #[cfg_attr(feature = "serde", serde(default))] + /// Use structured JSON mode. + pub json_schema: Option, +} + +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// The usage used from OpenAI. +pub struct OpenAIUsage { + /// The prompt tokens used. + pub prompt_tokens: u32, + /// The completion tokens used. + pub completion_tokens: u32, + /// The total tokens used. + pub total_tokens: u32, + /// Is the request cached? Useful for ignoring the tokens. + pub cached: bool, +} + +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// The results from OpenAI. +pub struct OpenAIReturn { + /// The response of the AI. + pub response: String, + /// The usage of the request. + pub usage: OpenAIUsage, + /// The error of the request if any. + pub error: Option, +} + +/// The OpenAI return type +// pub type OpenAIReturn = (String, OpenAIUsage); + +#[cfg(feature = "cache_openai")] +/// The OpenAI cache to use. +pub type AICache = moka::future::Cache; + +#[cfg(not(feature = "cache_openai"))] +/// The OpenAI cache to use. +pub type AICache = String; + +impl GPTConfigs { + /// GPTConfigs for OpenAI chrome dynamic scripting. + pub fn new(model: &str, prompt: &str, max_tokens: u16) -> GPTConfigs { + Self { + model: model.into(), + prompt: Prompt::Single(prompt.into()), + max_tokens, + ..Default::default() + } + } + + /// GPTConfigs for OpenAI chrome dynamic scripting and caching. + pub fn new_cache( + model: &str, + prompt: &str, + max_tokens: u16, + cache: Option, + ) -> GPTConfigs { + Self { + model: model.into(), + prompt: Prompt::Single(prompt.into()), + max_tokens, + cache, + ..Default::default() + } + } + + /// GPTConfigs for OpenAI chrome dynamic scripting multi chain prompts. + pub fn new_multi(model: &str, prompt: I, max_tokens: u16) -> GPTConfigs + where + I: IntoIterator, + S: AsRef, + { + Self { + model: model.into(), + prompt: Prompt::Multi(prompt.into_iter().map(|s| s.as_ref().to_string()).collect()), + max_tokens, + ..Default::default() + } + } + + /// GPTConfigs for OpenAI chrome dynamic scripting multi chain prompts with prompt caching. The feature flag 'cache_openai' is required. + pub fn new_multi_cache( + model: &str, + prompt: I, + max_tokens: u16, + cache: Option, + ) -> GPTConfigs + where + I: IntoIterator, + S: AsRef, + { + Self { + model: model.into(), + prompt: Prompt::Multi(prompt.into_iter().map(|s| s.as_ref().to_string()).collect()), + max_tokens, + cache, + ..Default::default() + } + } + + /// Set extra AI data to return results. + pub fn set_extra(&mut self, extra_ai_data: bool) -> &mut Self { + self.extra_ai_data = extra_ai_data; + self + } +} + +/// Custom deserialization for `Prompt` +#[cfg(feature = "serde")] +mod prompt_deserializer { + use super::Prompt; + use serde::{ + de::{self, SeqAccess, Visitor}, + Deserialize, Deserializer, + }; + use std::collections::VecDeque; + use std::fmt; + + struct PromptVisitor; + + impl<'de> Visitor<'de> for PromptVisitor { + type Value = Prompt; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string or an array of strings") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(Prompt::Single(value.to_owned())) + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let mut strings = VecDeque::new(); + while let Some(value) = seq.next_element()? { + strings.push_back(value); + } + Ok(Prompt::Multi(strings)) + } + } + + impl<'de> Deserialize<'de> for Prompt { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(PromptVisitor) + } + } +} + +#[test] +#[cfg(feature = "openai")] +fn deserialize_gpt_configs() { + let gpt_configs_json = "{\"prompt\":\"change background blue\",\"model\":\"gpt-3.5-turbo-16k\",\"max_tokens\":256,\"temperature\":0.54,\"top_p\":0.17}"; + let configs = crate::features::serde_json::from_str::(gpt_configs_json).ok(); + assert!(configs.is_some()) +} diff --git a/spider/src/features/search.rs b/spider/src/features/search.rs new file mode 100644 index 000000000..2e742a193 --- /dev/null +++ b/spider/src/features/search.rs @@ -0,0 +1,285 @@ +//! Web search integration for Spider. +//! +//! This module provides search provider abstractions and implementations +//! for integrating web search APIs (Serper, Brave, Bing, Tavily) with Spider's +//! crawling and automation capabilities. + +use std::fmt; + +/// Search provider trait for abstracting different search APIs. +pub trait SearchProvider: Send + Sync { + /// Execute a search query and return results. + /// + /// The client parameter allows reusing an existing HTTP client, + /// or passing `None` to use the provider's default client. + /// Note: Uses `reqwest::Client` directly since search APIs don't need caching middleware. + fn search( + &self, + query: &str, + options: &SearchOptions, + client: Option<&reqwest::Client>, + ) -> impl std::future::Future> + Send; + + /// Provider name for logging/debugging. + fn provider_name(&self) -> &'static str; +} + +/// Search options controlling result count, filters, etc. +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SearchOptions { + /// Maximum number of results to return. + pub limit: Option, + /// Country/region code (e.g., "us", "uk"). + pub country: Option, + /// Language code (e.g., "en", "es"). + pub language: Option, + /// Filter to specific domains. + pub site_filter: Option>, + /// Exclude specific domains. + pub exclude_domains: Option>, + /// Time range filter. + pub time_range: Option, + /// Include only pages with these words. + pub include_keywords: Option>, +} + +impl SearchOptions { + /// Create new search options with default values. + pub fn new() -> Self { + Self::default() + } + + /// Set maximum number of results. + pub fn with_limit(mut self, limit: usize) -> Self { + self.limit = Some(limit); + self + } + + /// Set country/region code. + pub fn with_country(mut self, country: impl Into) -> Self { + self.country = Some(country.into()); + self + } + + /// Set language code. + pub fn with_language(mut self, language: impl Into) -> Self { + self.language = Some(language.into()); + self + } + + /// Filter results to specific domains. + pub fn with_site_filter(mut self, domains: Vec) -> Self { + self.site_filter = Some(domains); + self + } + + /// Exclude specific domains from results. + pub fn with_exclude_domains(mut self, domains: Vec) -> Self { + self.exclude_domains = Some(domains); + self + } + + /// Set time range filter. + pub fn with_time_range(mut self, range: TimeRange) -> Self { + self.time_range = Some(range); + self + } +} + +/// Time range for filtering search results. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TimeRange { + /// Results from the past day. + Day, + /// Results from the past week. + Week, + /// Results from the past month. + Month, + /// Results from the past year. + Year, + /// Custom date range. + Custom { + /// Start date (format depends on provider). + start: String, + /// End date (format depends on provider). + end: String, + }, +} + +/// Unified search result format. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SearchResults { + /// The original query. + pub query: String, + /// List of organic results. + pub results: Vec, + /// Total result count (if available from provider). + pub total_results: Option, + /// Provider-specific metadata. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub metadata: Option, +} + +impl SearchResults { + /// Create new search results. + pub fn new(query: impl Into) -> Self { + Self { + query: query.into(), + results: Vec::new(), + total_results: None, + metadata: None, + } + } + + /// Add a result. + pub fn push(&mut self, result: SearchResult) { + self.results.push(result); + } + + /// Get URLs from results. + pub fn urls(&self) -> Vec<&str> { + self.results.iter().map(|r| r.url.as_str()).collect() + } + + /// Check if results are empty. + pub fn is_empty(&self) -> bool { + self.results.is_empty() + } + + /// Get number of results. + pub fn len(&self) -> usize { + self.results.len() + } +} + +/// Individual search result. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SearchResult { + /// Result title. + pub title: String, + /// Result URL. + pub url: String, + /// Snippet/description. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub snippet: Option, + /// Publication/index date. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub date: Option, + /// Result position (1-indexed). + pub position: usize, + /// Relevance score (provider-dependent). + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub score: Option, +} + +impl SearchResult { + /// Create a new search result. + pub fn new(title: impl Into, url: impl Into, position: usize) -> Self { + Self { + title: title.into(), + url: url.into(), + snippet: None, + date: None, + position, + score: None, + } + } + + /// Set snippet/description. + pub fn with_snippet(mut self, snippet: impl Into) -> Self { + self.snippet = Some(snippet.into()); + self + } + + /// Set date. + pub fn with_date(mut self, date: impl Into) -> Self { + self.date = Some(date.into()); + self + } + + /// Set relevance score. + pub fn with_score(mut self, score: f32) -> Self { + self.score = Some(score); + self + } +} + +/// Search error types. +#[derive(Debug)] +pub enum SearchError { + /// API request failed. + RequestFailed(String), + /// Invalid API key or authentication. + AuthenticationFailed, + /// Rate limit exceeded. + RateLimited, + /// Invalid query or parameters. + InvalidQuery(String), + /// Provider-specific error. + ProviderError(String), + /// No search provider configured. + NoProvider, +} + +impl fmt::Display for SearchError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::RequestFailed(msg) => write!(f, "Search request failed: {}", msg), + Self::AuthenticationFailed => write!(f, "Search authentication failed"), + Self::RateLimited => write!(f, "Search rate limit exceeded"), + Self::InvalidQuery(msg) => write!(f, "Invalid search query: {}", msg), + Self::ProviderError(msg) => write!(f, "Search provider error: {}", msg), + Self::NoProvider => write!(f, "No search provider configured"), + } + } +} + +impl std::error::Error for SearchError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_options_builder() { + let opts = SearchOptions::new() + .with_limit(10) + .with_country("us") + .with_language("en"); + + assert_eq!(opts.limit, Some(10)); + assert_eq!(opts.country.as_deref(), Some("us")); + assert_eq!(opts.language.as_deref(), Some("en")); + } + + #[test] + fn test_search_result_builder() { + let result = SearchResult::new("Test Title", "https://example.com", 1) + .with_snippet("Test snippet") + .with_score(0.95); + + assert_eq!(result.title, "Test Title"); + assert_eq!(result.url, "https://example.com"); + assert_eq!(result.position, 1); + assert_eq!(result.snippet.as_deref(), Some("Test snippet")); + assert_eq!(result.score, Some(0.95)); + } + + #[test] + fn test_search_results() { + let mut results = SearchResults::new("test query"); + results.push(SearchResult::new("Title 1", "https://example1.com", 1)); + results.push(SearchResult::new("Title 2", "https://example2.com", 2)); + + assert_eq!(results.len(), 2); + assert!(!results.is_empty()); + assert_eq!( + results.urls(), + vec!["https://example1.com", "https://example2.com"] + ); + } +} diff --git a/spider/src/features/search_providers/bing.rs b/spider/src/features/search_providers/bing.rs new file mode 100644 index 000000000..f89377dc0 --- /dev/null +++ b/spider/src/features/search_providers/bing.rs @@ -0,0 +1,181 @@ +//! Bing Web Search API provider implementation. +//! +//! Microsoft Bing provides web search via Azure Cognitive Services. + +use super::{SearchError, SearchOptions, SearchProvider, SearchResult, SearchResults}; + +/// Default Bing Search API endpoint. +const DEFAULT_API_URL: &str = "https://api.bing.microsoft.com/v7.0/search"; + +/// Bing Web Search API provider. +/// +/// Provides access to Microsoft Bing web search via Azure Cognitive Services. +/// +/// # Example +/// ```ignore +/// use spider::features::search_providers::BingProvider; +/// use spider::features::search::{SearchOptions, SearchProvider}; +/// +/// let provider = BingProvider::new("your-api-key"); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), None).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct BingProvider { + api_key: String, + api_url: Option, +} + +impl BingProvider { + /// Create a new Bing provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +impl SearchProvider for BingProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: Option<&reqwest::Client>, + ) -> Result { + // Build query parameters + let mut params = vec![("q", query.to_string())]; + + if let Some(limit) = options.limit { + params.push(("count", limit.min(50).to_string())); + } + + if let Some(ref country) = options.country { + params.push(("cc", country.clone())); + } + + if let Some(ref language) = options.language { + params.push(("setLang", language.clone())); + } + + // Use provided client or create a new one + let response = if let Some(c) = client { + c.get(self.endpoint()) + .header("Ocp-Apim-Subscription-Key", &self.api_key) + .query(¶ms) + .send() + .await + } else { + let c = reqwest::ClientBuilder::new() + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + c.get(self.endpoint()) + .header("Ocp-Apim-Subscription-Key", &self.api_key) + .query(¶ms) + .send() + .await + }; + + let response = response.map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Bing API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract web pages + let mut results = SearchResults::new(query); + + if let Some(pages) = json + .get("webPages") + .and_then(|v| v.get("value")) + .and_then(|v| v.as_array()) + { + for (i, item) in pages.iter().enumerate() { + let title = item + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("snippet").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(date) = item.get("dateLastCrawled").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Extract total results + if let Some(total) = json + .get("webPages") + .and_then(|v| v.get("totalEstimatedMatches")) + .and_then(|v| v.as_u64()) + { + results.total_results = Some(total); + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "bing" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bing_provider_new() { + let provider = BingProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + } + + #[test] + fn test_bing_provider_custom_url() { + let provider = BingProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider/src/features/search_providers/brave.rs b/spider/src/features/search_providers/brave.rs new file mode 100644 index 000000000..e3a84749f --- /dev/null +++ b/spider/src/features/search_providers/brave.rs @@ -0,0 +1,174 @@ +//! Brave Search API provider implementation. +//! +//! Brave provides privacy-focused web search with good quality results. + +use super::{SearchError, SearchOptions, SearchProvider, SearchResult, SearchResults}; + +/// Default Brave Search API endpoint. +const DEFAULT_API_URL: &str = "https://api.search.brave.com/res/v1/web/search"; + +/// Brave Search API provider. +/// +/// Provides access to Brave's privacy-focused web search API. +/// +/// # Example +/// ```ignore +/// use spider::features::search_providers::BraveProvider; +/// use spider::features::search::{SearchOptions, SearchProvider}; +/// +/// let provider = BraveProvider::new("your-api-key"); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), None).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct BraveProvider { + api_key: String, + api_url: Option, +} + +impl BraveProvider { + /// Create a new Brave provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +impl SearchProvider for BraveProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: Option<&reqwest::Client>, + ) -> Result { + // Build query parameters + let mut params = vec![("q", query.to_string())]; + + if let Some(limit) = options.limit { + params.push(("count", limit.min(20).to_string())); + } + + if let Some(ref country) = options.country { + params.push(("country", country.clone())); + } + + if let Some(ref language) = options.language { + params.push(("search_lang", language.clone())); + } + + // Use provided client or create a new one + let response = if let Some(c) = client { + c.get(self.endpoint()) + .header("X-Subscription-Token", &self.api_key) + .header("Accept", "application/json") + .query(¶ms) + .send() + .await + } else { + let c = reqwest::ClientBuilder::new() + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + c.get(self.endpoint()) + .header("X-Subscription-Token", &self.api_key) + .header("Accept", "application/json") + .query(¶ms) + .send() + .await + }; + + let response = response.map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Brave API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract web results + let mut results = SearchResults::new(query); + + if let Some(web) = json + .get("web") + .and_then(|v| v.get("results")) + .and_then(|v| v.as_array()) + { + for (i, item) in web.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("description").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(age) = item.get("age").and_then(|v| v.as_str()) { + result = result.with_date(age); + } + + results.push(result); + } + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "brave" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_brave_provider_new() { + let provider = BraveProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + } + + #[test] + fn test_brave_provider_custom_url() { + let provider = BraveProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider/src/features/search_providers/mod.rs b/spider/src/features/search_providers/mod.rs new file mode 100644 index 000000000..0546cc15e --- /dev/null +++ b/spider/src/features/search_providers/mod.rs @@ -0,0 +1,24 @@ +//! Search provider implementations. +//! +//! This module contains implementations of the [`super::search::SearchProvider`] trait +//! for various web search APIs. + +#[cfg(feature = "search_bing")] +mod bing; +#[cfg(feature = "search_brave")] +mod brave; +#[cfg(feature = "search_serper")] +mod serper; +#[cfg(feature = "search_tavily")] +mod tavily; + +#[cfg(feature = "search_bing")] +pub use bing::BingProvider; +#[cfg(feature = "search_brave")] +pub use brave::BraveProvider; +#[cfg(feature = "search_serper")] +pub use serper::SerperProvider; +#[cfg(feature = "search_tavily")] +pub use tavily::TavilyProvider; + +pub use super::search::{SearchError, SearchOptions, SearchProvider, SearchResult, SearchResults}; diff --git a/spider/src/features/search_providers/serper.rs b/spider/src/features/search_providers/serper.rs new file mode 100644 index 000000000..b042337c2 --- /dev/null +++ b/spider/src/features/search_providers/serper.rs @@ -0,0 +1,206 @@ +//! Serper.dev search provider implementation. +//! +//! Serper provides high-quality Google SERP API access. + +use super::{SearchError, SearchOptions, SearchProvider, SearchResult, SearchResults}; + +/// Default Serper API endpoint. +const DEFAULT_API_URL: &str = "https://google.serper.dev/search"; + +/// Serper.dev search provider. +/// +/// Provides access to Google search results via the Serper API. +/// +/// # Example +/// ```ignore +/// use spider::features::search_providers::SerperProvider; +/// use spider::features::search::{SearchOptions, SearchProvider}; +/// +/// let provider = SerperProvider::new("your-api-key"); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), None).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct SerperProvider { + api_key: String, + api_url: Option, +} + +impl SerperProvider { + /// Create a new Serper provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + /// + /// This is useful for self-hosted or alternative Serper-compatible APIs. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +impl SearchProvider for SerperProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: Option<&reqwest::Client>, + ) -> Result { + // Build request body + let mut body = serde_json::json!({ + "q": query + }); + + if let Some(limit) = options.limit { + body["num"] = serde_json::json!(limit.min(100)); + } + + if let Some(ref country) = options.country { + body["gl"] = serde_json::json!(country); + } + + if let Some(ref language) = options.language { + body["hl"] = serde_json::json!(language); + } + + // Build the query with site filter if present + let query_with_filter = if let Some(ref sites) = options.site_filter { + let site_query = sites + .iter() + .map(|s| format!("site:{}", s)) + .collect::>() + .join(" OR "); + format!("{} ({})", query, site_query) + } else { + query.to_string() + }; + body["q"] = serde_json::json!(query_with_filter); + + // Use provided client or create a new one + let response = if let Some(c) = client { + c.post(self.endpoint()) + .header("X-API-KEY", &self.api_key) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + } else { + let c = reqwest::ClientBuilder::new() + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + c.post(self.endpoint()) + .header("X-API-KEY", &self.api_key) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + }; + + let response = response.map_err(|e| { + if e.is_timeout() { + SearchError::RequestFailed("Request timed out".to_string()) + } else if e.is_connect() { + SearchError::RequestFailed("Connection failed".to_string()) + } else { + SearchError::RequestFailed(e.to_string()) + } + })?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Serper API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract organic results + let mut results = SearchResults::new(query); + + if let Some(organic) = json.get("organic").and_then(|v| v.as_array()) { + for (i, item) in organic.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item + .get("link") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("snippet").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(date) = item.get("date").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Extract total results if available + if let Some(info) = json.get("searchInformation") { + if let Some(total) = info.get("totalResults").and_then(|v| v.as_str()) { + results.total_results = total.parse().ok(); + } + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "serper" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serper_provider_new() { + let provider = SerperProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + } + + #[test] + fn test_serper_provider_custom_url() { + let provider = + SerperProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider/src/features/search_providers/tavily.rs b/spider/src/features/search_providers/tavily.rs new file mode 100644 index 000000000..84df259c2 --- /dev/null +++ b/spider/src/features/search_providers/tavily.rs @@ -0,0 +1,195 @@ +//! Tavily AI Search API provider implementation. +//! +//! Tavily provides AI-optimized search designed for LLM applications. + +use super::{SearchError, SearchOptions, SearchProvider, SearchResult, SearchResults}; + +/// Default Tavily API endpoint. +const DEFAULT_API_URL: &str = "https://api.tavily.com/search"; + +/// Tavily AI Search API provider. +/// +/// Provides access to Tavily's AI-optimized search API. +/// +/// # Example +/// ```ignore +/// use spider::features::search_providers::TavilyProvider; +/// use spider::features::search::{SearchOptions, SearchProvider}; +/// +/// let provider = TavilyProvider::new("your-api-key"); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), None).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct TavilyProvider { + api_key: String, + api_url: Option, + /// Search depth: "basic" or "advanced". + search_depth: String, +} + +impl TavilyProvider { + /// Create a new Tavily provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + search_depth: "basic".to_string(), + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Set search depth to "advanced" for more thorough results. + pub fn with_advanced_search(mut self) -> Self { + self.search_depth = "advanced".to_string(); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +impl SearchProvider for TavilyProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: Option<&reqwest::Client>, + ) -> Result { + // Build request body + let mut body = serde_json::json!({ + "api_key": &self.api_key, + "query": query, + "search_depth": &self.search_depth + }); + + if let Some(limit) = options.limit { + body["max_results"] = serde_json::json!(limit.min(10)); + } + + // Tavily supports include/exclude domains + if let Some(ref sites) = options.site_filter { + body["include_domains"] = serde_json::json!(sites); + } + + if let Some(ref exclude) = options.exclude_domains { + body["exclude_domains"] = serde_json::json!(exclude); + } + + // Use provided client or create a new one + let response = if let Some(c) = client { + c.post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + } else { + let c = reqwest::ClientBuilder::new() + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + c.post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + }; + + let response = response.map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Tavily API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract results + let mut results = SearchResults::new(query); + + if let Some(items) = json.get("results").and_then(|v| v.as_array()) { + for (i, item) in items.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("content").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + // Tavily provides a relevance score + if let Some(score) = item.get("score").and_then(|v| v.as_f64()) { + result = result.with_score(score as f32); + } + + if let Some(date) = item.get("published_date").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Store raw metadata (includes Tavily's AI-generated answer if available) + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "tavily" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tavily_provider_new() { + let provider = TavilyProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + assert_eq!(provider.search_depth, "basic"); + } + + #[test] + fn test_tavily_provider_advanced() { + let provider = TavilyProvider::new("test-key").with_advanced_search(); + assert_eq!(provider.search_depth, "advanced"); + } + + #[test] + fn test_tavily_provider_custom_url() { + let provider = + TavilyProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider/src/features/solvers.rs b/spider/src/features/solvers.rs new file mode 100644 index 000000000..bb90eacec --- /dev/null +++ b/spider/src/features/solvers.rs @@ -0,0 +1,2618 @@ +use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; + +#[cfg(all(feature = "chrome", feature = "real_browser"))] +use chromiumoxide::{ + cdp::js_protocol::runtime::{CallFunctionOnParams, EvaluateParams}, + error::CdpError, + layout::Point, + Page, +}; +use std::time::Duration; + +#[cfg(all(feature = "chrome", feature = "real_browser"))] +use crate::utils::{page_wait, perform_smart_mouse_movement, RequestError, CF_WAIT_FOR}; +#[cfg(all(feature = "chrome", feature = "real_browser"))] +use base64::prelude::*; + +static VERIFY_PATTERNS: &[&[u8]] = &[ + b"verifying you are human", + b"review the security of your connection", + b"please verify you are a human", + b"checking your browser before accessing", + b"prove you are human", + b"checking if the site connection is secure", +]; + +/// Imperva iframe patterns. +static IMPERVA_IFRAME_PATTERNS: &[&[u8]] = &[ + b"geo.captcha-delivery.com", + b"captcha-delivery.com", + b"Verification system", + b"Verification Required", + b"Verification successful", + b"Verifying device", +]; + +/// Recaptcha iframe patterns. +static RECAPTCHA_PATTERNS: &[&[u8]] = &[ + b"https://www.google.com/recaptcha/", + b"/recaptcha/", + b"recaptcha/api2/anchor", + b"recaptcha/enterprise/bframe", +]; + +/// Geetest patterns. +static GEETEST_PATTERNS: &[&[u8]] = &[ + b"id=\"embed-captcha\"", + b"class=\"gee-test", + b"class=\"gee-test__placeholder", +]; + +/// Geetest loading patterns. +static GEETEST_LOADING_PATTERNS: &[&[u8]] = &[b"Loading GeeTest", b"geetest_wait", b"geetest_init"]; + +/// Geetest visible patterns. +static GEETEST_VISIBLE_PATTERNS: &[&[u8]] = &[ + b"geetest_widget", + b"geetest_slider_button", + b"geetest_canvas", + b"geetest_canvas_slice", +]; + +/// Imperva wait patterns. +static IMPERVA_WAIT_PATTERNS: &[&[u8]] = &[ + b"Verifying the device", + b"Verifying the device...", + b"The requested content will be available after verification", + b"available after verification", +]; + +/// Imperva iframe phase patterns. +static IMPERVA_IFRAME_PHASE_PATTERNS: &[&[u8]] = &[ + b"geo.captcha-delivery.com", + b"captcha-delivery.com", + b"Verification system", +]; + +/// Hcaptcha wait patterns. +#[cfg(all(feature = "chrome", feature = "real_browser"))] +static HCAPTCHA_IFRAME_PATTERNS: &[&[u8]] = &[ + b"newassets.hcaptcha.com", + b"hcaptcha.com/captcha", + b"Widget containing checkbox for hCaptcha", + b"data-hcaptcha-widget-id", +]; + +/// RC enterprise guards. +#[cfg(all(feature = "chrome", feature = "real_browser"))] +static RC_ENTERPRISE_GUARD_PATTERNS: &[&[u8]] = &[ + b"__recaptcha_api", + b"/recaptcha/enterprise/", + b"rc-imageselect", + b"rc-imageselect-tile", +]; + +static LEMIN_PATTERNS: &[&[u8]] = &[b"id=\"lemin-cropped-captcha\""]; + +/// RC verify btn patterns. +#[cfg(all(feature = "chrome", feature = "real_browser"))] +static RC_VERIFY_BUTTON_PATTERNS: &[&[u8]] = &[b"id=\"recaptcha-verify-button\"", b">Verify<"]; + +/// RC tile patterns. +#[cfg(all(feature = "chrome", feature = "real_browser"))] +static RC_TILE_CLASS_PATTERNS: &[&[u8]] = &[b"rc-imageselect-tile"]; + +#[cfg(all(feature = "chrome", feature = "real_browser"))] +lazy_static! { + /// hCaptcha‑iframe matcher (used inside Imperva flow) + static ref HCAPTCHA_IFRAME_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(HCAPTCHA_IFRAME_PATTERNS) + .expect("valid hCaptcha iframe patterns"); + + static ref RC_ENTERPRISE_GUARD_AC: AhoCorasick = AhoCorasickBuilder::new() + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .ascii_case_insensitive(false) + .build(RC_ENTERPRISE_GUARD_PATTERNS) + .expect("valid enterprise‑recaptcha guard patterns"); + + // Verify‑button detection (either the hidden button id or the visible “Verify” text). + static ref RC_VERIFY_BUTTON_AC: AhoCorasick = AhoCorasickBuilder::new() + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .ascii_case_insensitive(false) + .build(RC_VERIFY_BUTTON_PATTERNS) + .expect("valid verify‑button patterns"); + + // Tile‑class matcher – used to locate every tile in the HTML. + static ref RC_TILE_CLASS_AC: AhoCorasick = AhoCorasickBuilder::new() + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .ascii_case_insensitive(false) + .build(RC_TILE_CLASS_PATTERNS) + .expect("valid tile‑class pattern"); +} + +#[cfg(any(not(feature = "wreq"), feature = "cache_request"))] +lazy_static! { + /// Gemini client – plain reqwest client (no middleware). + static ref GEMINI_CLIENT: reqwest::Client = { + reqwest::ClientBuilder::new() + .timeout(Duration::from_millis(20_000)) + .build() + .expect("failed to build Gemini client") + }; +} +#[cfg(all(feature = "wreq", not(feature = "cache_request")))] +lazy_static! { + /// Gemini client – plain wreq client (no middleware). + static ref GEMINI_CLIENT: wreq::Client = { + wreq::ClientBuilder::new() + .timeout(Duration::from_millis(20_000)) + .build() + .expect("failed to build Gemini client") + }; +} + +lazy_static! { + /// Imperva check + static ref AC_IMPERVA_IFRAME: aho_corasick::AhoCorasick = aho_corasick::AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(IMPERVA_IFRAME_PATTERNS) + .expect("valid imperva iframe patterns"); + /// Bot verify. + static ref AC: AhoCorasick = aho_corasick::AhoCorasickBuilder::new() + .match_kind(aho_corasick::MatchKind::LeftmostLongest) + .build(VERIFY_PATTERNS) + .unwrap(); + /// Recaptcha patterns. + static ref RECAPTCHA_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(RECAPTCHA_PATTERNS) + .expect("valid recaptcha patterns"); + /// GeeTest patterns. + static ref GEETEST_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(GEETEST_PATTERNS) + .expect("valid geetest patterns"); + /// GeeTest loading AC. + static ref GEETEST_LOADING_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(GEETEST_LOADING_PATTERNS) + .expect("valid geetest loading patterns"); + /// GeeTest visible‑challenge matcher + static ref GEETEST_VISIBLE_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(GEETEST_VISIBLE_PATTERNS) + .expect("valid geetest visible patterns"); + /// Imperva wait AC. + static ref IMPERVA_WAIT_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(IMPERVA_WAIT_PATTERNS) + .expect("valid Imperva wait‑screen patterns"); + + /// Imperva iframe matcher. + static ref IMPERVA_IFRAME_PHASE_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(IMPERVA_IFRAME_PHASE_PATTERNS) + .expect("valid Imperva iframe‑phase patterns"); + /// Lemin match. + static ref LEMIN_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(aho_corasick::MatchKind::LeftmostFirst) + .build(LEMIN_PATTERNS) + .expect("valid lemin patterns"); +} + +#[cfg(feature = "chrome")] +/// CF prefix scan bytes. +const CF_PREFIX_SCAN_BYTES: usize = 120; + +#[cfg(feature = "chrome")] +#[inline(always)] +/// CF slice prefix. +fn cf_prefix_slice(b: &[u8]) -> &[u8] { + if b.len() > CF_PREFIX_SCAN_BYTES { + &b[..CF_PREFIX_SCAN_BYTES] + } else { + b + } +} + +#[cfg(feature = "chrome")] +lazy_static! { + /// CF end match. + static ref CF_END: &'static [u8; 62] = + b"target=\"_blank\">Cloudflare"; + /// CF end second template. + static ref CF_END2: &'static [u8; 72] = + b"Performance & security by Cloudflare"; + /// CF head. + static ref CF_HEAD: &'static [u8; 34] = b"\n

Hello

"#; + let cleaned = clean_html_base(html); + assert!(!cleaned.contains("alert(1)")); + assert!(!cleaned.contains(".x{}")); + assert!(cleaned.contains("Hello")); + } + + #[test] + fn test_clean_html_slim() { + let html = r#"

Hello

"#; + let cleaned = clean_html_slim(html); + assert!(cleaned.contains("Hello")); + assert!(!cleaned.contains("")); + assert!(!cleaned.contains("No JS")); + } + + #[test] + fn test_clean_html_full() { + let html = r#"

Hello

Foot
"#; + let cleaned = clean_html_full(html); + assert!(cleaned.contains("Hello")); + assert!(!cleaned.contains("Menu")); + assert!(!cleaned.contains("Foot")); + } + + #[test] + fn test_get_last_segment() { + assert_eq!(get_last_segment("/foo/bar/baz"), "baz"); + assert_eq!(get_last_segment("/foo/bar/"), ""); + assert_eq!(get_last_segment("nopath"), "nopath"); + } + + #[test] + fn test_get_path_from_url() { + assert_eq!(get_path_from_url("https://example.com/foo/bar"), "/foo/bar"); + assert_eq!(get_path_from_url("https://example.com"), "/"); + } + + #[test] + fn test_get_domain_from_url() { + assert_eq!( + get_domain_from_url("https://example.com/path"), + "example.com" + ); + assert_eq!( + get_domain_from_url("https://sub.example.com/path"), + "sub.example.com" + ); + } + + #[test] + fn test_networking_capable() { + assert!(networking_capable("https://example.com")); + assert!(networking_capable("http://example.com")); + assert!(networking_capable("ftp://files.example.com")); + assert!(networking_capable("file:///local/path")); + assert!(!networking_capable("mailto:user@example.com")); + assert!(!networking_capable("javascript:void(0)")); + } + + #[test] + fn test_prepare_url_no_scheme() { + assert_eq!(prepare_url("example.com"), "https://example.com"); + } + + #[test] + fn test_prepare_url_http() { + assert_eq!( + prepare_url("http://example.com/path"), + "https://example.com/path" + ); + } + + #[test] + fn test_prepare_url_ftp() { + assert_eq!( + prepare_url("ftp://files.example.com/data"), + "https://files.example.com/data" + ); + } + + #[test] + fn test_prepare_url_https_passthrough() { + assert_eq!(prepare_url("https://example.com"), "https://example.com"); + } + + #[test] + fn test_prepare_url_with_port() { + assert_eq!( + prepare_url("http://localhost:8080/api"), + "https://localhost:8080/api" + ); + } + + #[test] + fn test_prepare_url_empty() { + assert_eq!(prepare_url(""), "https://"); + } + + #[test] + fn test_prepare_url_bare_domain_with_path() { + assert_eq!(prepare_url("example.com/page"), "https://example.com/page"); + } + + #[test] + fn test_is_html_content_check() { + assert!(is_html_content_check(b"")); + assert!(is_html_content_check(b"")); + assert!(!is_html_content_check(b"{ \"json\": true }")); + assert!(!is_html_content_check(b"plain text content")); + } + + #[test] + fn test_crawl_duration_expired() { + // None timeout → not expired + assert!(!crawl_duration_expired(&None, &None)); + assert!(!crawl_duration_expired( + &Some(Duration::from_secs(10)), + &None + )); + assert!(!crawl_duration_expired(&None, &Some(Instant::now()))); + + // Very long timeout → not expired + let start = Some(Instant::now()); + assert!(!crawl_duration_expired( + &Some(Duration::from_secs(3600)), + &start + )); + + // Zero timeout → expired immediately + assert!(crawl_duration_expired( + &Some(Duration::from_secs(0)), + &start + )); + } + + #[test] + fn test_split_hashset_round_robin() { + let mut set = HashSet::new(); + for i in 0..10 { + set.insert(i); + } + + let buckets = split_hashset_round_robin(set, 3); + assert_eq!(buckets.len(), 3); + let total: usize = buckets.iter().map(|b| b.len()).sum(); + assert_eq!(total, 10); + + // Single part + let mut set2 = HashSet::new(); + set2.insert(1); + set2.insert(2); + let buckets2 = split_hashset_round_robin(set2, 1); + assert_eq!(buckets2.len(), 1); + assert_eq!(buckets2[0].len(), 2); + } + + #[cfg(any(feature = "cache", feature = "cache_mem"))] + #[test] + fn test_create_cache_key_raw() { + assert_eq!( + create_cache_key_raw("https://example.com", None, None), + "GET:https://example.com" + ); + assert_eq!( + create_cache_key_raw("https://example.com", Some("POST"), None), + "POST:https://example.com" + ); + assert_eq!( + create_cache_key_raw("https://example.com", None, Some("token123")), + "GET:https://example.com:token123" + ); + } + + #[cfg(feature = "cache_chrome_hybrid")] + #[tokio::test] + async fn test_fetch_page_html_raw_cached_uses_seeded_cache_entry() { + use std::collections::HashMap; + + let target_url = "https://cache-unit-test.invalid/path"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = HashMap::new(); + response_headers.insert("accept-language".to_string(), "en-US".to_string()); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = b"cached-response".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers.clone(), + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = HashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let client = reqwest_middleware::ClientBuilder::new( + reqwest::ClientBuilder::new() + .build() + .expect("build reqwest client"), + ) + .build(); + + let cache_options = Some(CacheOptions::Yes); + let cache_policy = None; + + let page = + fetch_page_html_raw_cached(target_url, &client, cache_options, &cache_policy).await; + assert_eq!(page.status_code, StatusCode::OK); + + let content = String::from_utf8_lossy( + page.content + .as_ref() + .expect("cached response content") + .as_ref(), + ); + assert!(content.contains("cached-response")); + } + + #[cfg(feature = "cache_chrome_hybrid")] + #[tokio::test] + async fn test_fetch_page_html_raw_cached_performance_seeded_vs_network() { + use std::collections::HashMap; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::time::Duration as StdDuration; + + let listener = TcpListener::bind("127.0.0.1:0").expect("bind local delayed server"); + let addr = listener.local_addr().expect("read local addr"); + + let response_body = "network-delayed-response".to_string(); + let response_body_clone = response_body.clone(); + + let server_thread = std::thread::spawn(move || { + let (mut stream, _) = listener.accept().expect("accept local connection"); + let mut request_buf = [0_u8; 1024]; + let _ = stream.read(&mut request_buf); + std::thread::sleep(StdDuration::from_millis(350)); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + response_body_clone.len(), + response_body_clone + ); + + stream + .write_all(response.as_bytes()) + .expect("write delayed response"); + stream.flush().expect("flush delayed response"); + }); + + let target_url = format!("http://{}/perf-cache-test", addr); + + let client = reqwest_middleware::ClientBuilder::new( + reqwest::ClientBuilder::new() + .build() + .expect("build reqwest client"), + ) + .build(); + + let network_start = tokio::time::Instant::now(); + let network_page = fetch_page_html_raw(&target_url, &client).await; + let network_duration = network_start.elapsed(); + + server_thread + .join() + .expect("join delayed local server thread"); + + assert_eq!(network_page.status_code, StatusCode::OK); + + let cache_key = create_cache_key_raw(&target_url, None, None); + let mut response_headers = HashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let http_response = HttpResponse { + body: response_body.into_bytes(), + headers: response_headers, + status: 200, + url: Url::parse(&target_url).expect("valid cache url"), + version: HttpVersion::Http11, + }; + + let request_headers = HashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let cache_options = Some(CacheOptions::SkipBrowser); + let cache_policy = None; + + let cached_start = tokio::time::Instant::now(); + let cached_page = + fetch_page_html_raw_cached(&target_url, &client, cache_options, &cache_policy).await; + let cached_duration = cached_start.elapsed(); + + assert_eq!(cached_page.status_code, StatusCode::OK); + assert!( + cached_duration < network_duration, + "expected cached path to be faster (network={}ms cached={}ms)", + network_duration.as_millis(), + cached_duration.as_millis() + ); + + let cached_secs = cached_duration.as_secs_f64().max(0.000_001); + let speedup = network_duration.as_secs_f64() / cached_secs; + + eprintln!( + "cache performance: network={}ms cached={}ms speedup={:.2}x", + network_duration.as_millis(), + cached_duration.as_millis(), + speedup + ); + } + + /// Verify that Chrome-rendered pages with no-cache are still cacheable. + /// put_hybrid_cache overrides no-cache → max-age=172800 for the policy, + /// so Period(now - 2d) correctly treats recently-stored entries as fresh. + #[cfg(any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem"))] + #[tokio::test] + async fn test_put_hybrid_cache_overrides_no_cache_for_policy() { + use std::collections::HashMap; + + let target_url = "https://no-cache-override.test/page"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = HashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert("cache-control".to_string(), "no-cache".to_string()); + + let body = b"no-cache-but-cacheable".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + put_hybrid_cache(&cache_key, http_response, "GET", HashMap::new()).await; + + // Period(now - 2d): entry was just stored → age ≈ 0 < max_age(172800) → fresh + let two_days_ago = std::time::SystemTime::now() + .checked_sub(std::time::Duration::from_secs(2 * 24 * 3600)) + .unwrap(); + let cache_policy_period = Some(super::BasicCachePolicy::Period(two_days_ago)); + let result = get_cached_url_base( + target_url, + Some(CacheOptions::SkipBrowser), + &cache_policy_period, + ) + .await; + assert!( + result.is_some(), + "no-cache response should be cached via policy override" + ); + assert!(result.unwrap().contains("no-cache-but-cacheable")); + + // Normal policy also returns it (freshly stored, max-age=172800 > age≈0) + let cache_policy_normal = Some(super::BasicCachePolicy::Normal); + let result_normal = get_cached_url_base( + target_url, + Some(CacheOptions::SkipBrowser), + &cache_policy_normal, + ) + .await; + assert!( + result_normal.is_some(), + "freshly stored entry should be fresh under Normal policy" + ); + } + + /// Verify no-store is also overridden on write. + #[cfg(any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem"))] + #[tokio::test] + async fn test_put_hybrid_cache_overrides_no_store_for_policy() { + use std::collections::HashMap; + + let target_url = "https://no-store-override.test/page"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = HashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert("cache-control".to_string(), "no-store".to_string()); + + let body = b"no-store-but-cacheable".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + put_hybrid_cache(&cache_key, http_response, "GET", HashMap::new()).await; + + let two_days_ago = std::time::SystemTime::now() + .checked_sub(std::time::Duration::from_secs(2 * 24 * 3600)) + .unwrap(); + let cache_policy_period = Some(super::BasicCachePolicy::Period(two_days_ago)); + let result = get_cached_url_base( + target_url, + Some(CacheOptions::SkipBrowser), + &cache_policy_period, + ) + .await; + assert!( + result.is_some(), + "no-store response should be cached via policy override" + ); + assert!(result.unwrap().contains("no-store-but-cacheable")); + } + + /// Verify that last-modified heuristic is respected (no override needed). + #[cfg(any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem"))] + #[tokio::test] + async fn test_put_hybrid_cache_respects_last_modified_heuristic() { + use std::collections::HashMap; + + let target_url = "https://last-modified-heuristic.test/page"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = HashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + // No cache-control, but has last-modified → heuristic gives max_age + response_headers.insert( + "last-modified".to_string(), + "Wed, 08 Feb 2023 21:02:33 GMT".to_string(), + ); + + let body = b"heuristic-cached".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + put_hybrid_cache(&cache_key, http_response, "GET", HashMap::new()).await; + + // last-modified from 2023 → heuristic max-age ≈ 109 days → fresh + let two_days_ago = std::time::SystemTime::now() + .checked_sub(std::time::Duration::from_secs(2 * 24 * 3600)) + .unwrap(); + let cache_policy_period = Some(super::BasicCachePolicy::Period(two_days_ago)); + let result = get_cached_url_base( + target_url, + Some(CacheOptions::SkipBrowser), + &cache_policy_period, + ) + .await; + assert!( + result.is_some(), + "last-modified heuristic should make entry fresh" + ); + assert!(result.unwrap().contains("heuristic-cached")); + } + + /// Verify that Set-Cookie doesn't prevent caching (shared=false). + #[cfg(any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem"))] + #[tokio::test] + async fn test_put_hybrid_cache_set_cookie_does_not_block() { + use std::collections::HashMap; + + let target_url = "https://set-cookie-cache.test/page"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = HashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + response_headers.insert( + "set-cookie".to_string(), + "session=abc123; Path=/".to_string(), + ); + + let body = b"set-cookie-cached".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + put_hybrid_cache(&cache_key, http_response, "GET", HashMap::new()).await; + + let two_days_ago = std::time::SystemTime::now() + .checked_sub(std::time::Duration::from_secs(2 * 24 * 3600)) + .unwrap(); + let cache_policy_period = Some(super::BasicCachePolicy::Period(two_days_ago)); + let result = get_cached_url_base( + target_url, + Some(CacheOptions::SkipBrowser), + &cache_policy_period, + ) + .await; + assert!( + result.is_some(), + "Set-Cookie should not block caching with shared=false" + ); + assert!(result.unwrap().contains("set-cookie-cached")); + } + + #[test] + fn test_is_cacheable_body_empty_truly_empty() { + assert!(is_cacheable_body_empty(b"")); + assert!(is_cacheable_body_empty(b" ")); + assert!(is_cacheable_body_empty(b"\n\t \r\n")); + } + + #[test] + fn test_is_cacheable_body_empty_skeleton_html() { + assert!(is_cacheable_body_empty( + b"" + )); + assert!(is_cacheable_body_empty(b"")); + } + + #[test] + fn test_is_cacheable_body_empty_html_empty_body() { + assert!(is_cacheable_body_empty( + b"x " + )); + } + + #[test] + fn test_is_cacheable_body_empty_html_with_content() { + assert!(!is_cacheable_body_empty( + b"

Hello

" + )); + } + + #[test] + fn test_is_cacheable_body_empty_json_skips_html_checks() { + assert!(!is_cacheable_body_empty(b"{}")); + assert!(!is_cacheable_body_empty(b"{\"key\": \"value\"}")); + assert!(!is_cacheable_body_empty(b"[1,2,3]")); + assert!(!is_cacheable_body_empty(b"null")); + } + + #[test] + fn test_is_cacheable_body_empty_css_js_skip_html_checks() { + assert!(!is_cacheable_body_empty(b"body { color: red; }")); + assert!(!is_cacheable_body_empty(b"function foo() { return 1; }")); + assert!(!is_cacheable_body_empty(b"export default {}")); + } + + #[test] + fn test_is_cacheable_body_empty_binary_skip_html_checks() { + // PNG header + assert!(!is_cacheable_body_empty(&[ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A + ])); + // JPEG header + assert!(!is_cacheable_body_empty(&[0xFF, 0xD8, 0xFF, 0xE0])); + // Arbitrary binary + assert!(!is_cacheable_body_empty(&[0x00, 0x01, 0x02, 0x03])); + } + + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + #[test] + fn test_decode_cached_html_bytes_rejects_empty_html() { + // Empty shell HTML must be treated as a cache miss (returns None) + assert!( + decode_cached_html_bytes(b"", None).is_none() + ); + assert!(decode_cached_html_bytes(b"", None).is_none()); + assert!(decode_cached_html_bytes(b"", None).is_none()); + assert!(decode_cached_html_bytes(b" ", None).is_none()); + // Real content must still be returned + assert!( + decode_cached_html_bytes(b"

Hello

", None).is_some() + ); + } +} diff --git a/spider/src/utils/templates.rs b/spider/src/utils/templates.rs new file mode 100644 index 000000000..2577f6982 --- /dev/null +++ b/spider/src/utils/templates.rs @@ -0,0 +1,24 @@ +lazy_static! { + /// Apache server forbidden. + pub static ref APACHE_FORBIDDEN: &'static [u8; 317] = br#" + +403 Forbidden + +

Forbidden

+

You don't have permission to access this resource.

+

Additionally, a 403 Forbidden +error was encountered while trying to use an ErrorDocument to handle the request.

+"#; + + /// Open Resty forbidden. + pub static ref OPEN_RESTY_FORBIDDEN: &'static [u8; 125] = br#"403 Forbidden + +

403 Forbidden

+
openresty
"#; + + + /// Empty HTML. + pub static ref EMPTY_HTML: &'static [u8; 39] = b""; + /// Empty html. + pub static ref EMPTY_HTML_BASIC: &'static [u8; 13] = b""; +} diff --git a/spider/src/utils/trie.rs b/spider/src/utils/trie.rs new file mode 100644 index 000000000..de55afd6c --- /dev/null +++ b/spider/src/utils/trie.rs @@ -0,0 +1,291 @@ +use hashbrown::HashMap; +use std::fmt::Debug; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// TrieNode structure to handle clean url path mappings. +pub struct TrieNode { + /// The children for the trie. + pub children: HashMap>, + /// The value for the trie. + pub value: Option, +} + +impl TrieNode { + /// A new trie node. + pub fn new() -> Self { + TrieNode { + children: HashMap::new(), + value: None, + } + } +} + +impl Default for TrieNode { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Trie value. +pub struct Trie { + /// A new trie node. + pub root: TrieNode, + /// Contains a match all segment to default to. + pub match_all: bool, +} + +impl Default for Trie { + fn default() -> Self { + Self::new() + } +} + +impl Trie { + /// A new trie node. + pub fn new() -> Self { + Self { + root: TrieNode::new(), + match_all: false, + } + } + + /// Get the byte offset where the path portion starts, stripping scheme+host. + #[inline] + fn path_start(path: &str) -> usize { + // Fast path for common protocols (avoids generic find("://") scan) + let after_scheme = if path.starts_with("https://") { + 8 + } else if path.starts_with("http://") { + 7 + } else if let Some(pos) = path.find("://") { + pos + 3 + } else { + return 0; + }; + + if after_scheme < path.len() { + // Find the first '/' after the host using memchr (via find on byte slice) + path.as_bytes()[after_scheme..] + .iter() + .position(|&b| b == b'/') + .map_or(path.len(), |p| after_scheme + p) + } else { + 0 + } + } + + /// Iterate path segments without allocating. + #[inline] + fn path_segments(path: &str) -> impl Iterator { + let start = Self::path_start(path); + let base = if start < path.len() { + &path[start..] + } else { + path + }; + base.split('/') + .filter(|s| !s.is_empty() && !s.contains('.')) + } + + /// Insert a path and its associated value into the trie. + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert(&mut self, path: &str, value: V) { + let mut node = &mut self.root; + + for segment in Self::path_segments(path) { + node = node + .children + .entry_ref(segment) + .or_insert_with(TrieNode::new); + } + + if path == "/" { + self.match_all = true; + } + + node.value = Some(value); + } + + /// Search for a path in the trie. + #[cfg_attr(feature = "inline-more", inline)] + pub fn search(&self, input: &str) -> Option<&V> { + let mut node = &self.root; + + if node.children.is_empty() && node.value.is_none() { + return None; + } + + for segment in Self::path_segments(input) { + if let Some(child) = node.children.get(segment) { + node = child; + } else if !self.match_all { + return None; + } + } + + node.value.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trie_node_new() { + let node: TrieNode = TrieNode::new(); + assert!(node.children.is_empty()); + assert!(node.value.is_none()); + } + + #[test] + fn test_trie_new() { + let trie: Trie = Trie::new(); + assert!(trie.root.children.is_empty()); + assert!(trie.root.value.is_none()); + } + + #[test] + fn test_insert_and_search() { + let mut trie: Trie = Trie::new(); + trie.insert("/path/to/node", 42); + trie.insert("https://mywebsite/path/to/node", 22); + + assert_eq!(trie.search("https://mywebsite/path/to/node"), Some(&22)); + assert_eq!(trie.search("/path/to/node"), Some(&22)); + assert_eq!(trie.search("/path"), None); + assert_eq!(trie.search("/path/to"), None); + assert_eq!(trie.search("/path/to/node/extra"), None); + + // insert match all context + trie.insert("/", 11); + assert_eq!(trie.search("/random"), Some(&11)); + } + + #[test] + fn test_insert_multiple_nodes() { + let mut trie: Trie = Trie::new(); + trie.insert("/path/to/node1", 1); + trie.insert("/path/to/node2", 2); + trie.insert("/path/to/node3", 3); + + assert_eq!(trie.search("/path/to/node1"), Some(&1)); + assert_eq!(trie.search("/path/to/node2"), Some(&2)); + assert_eq!(trie.search("/path/to/node3"), Some(&3)); + } + + #[test] + fn test_insert_overwrite() { + let mut trie: Trie = Trie::new(); + trie.insert("/path/to/node", 42); + trie.insert("/path/to/node", 84); + + assert_eq!(trie.search("/path/to/node"), Some(&84)); + } + + #[test] + fn test_search_nonexistent_path() { + let mut trie: Trie = Trie::new(); + trie.insert("/path/to/node", 42); + + assert!(trie.search("/nonexistent").is_none()); + assert!(trie.search("/path/to/wrongnode").is_none()); + } + + #[test] + fn test_trie_empty_path() { + let mut trie: Trie = Trie::new(); + trie.insert("", 1); + // Empty path normalizes to "/" which sets match_all + assert!(trie.search("").is_some() || trie.search("/anything").is_some()); + } + + #[test] + fn test_trie_unicode_paths() { + let mut trie: Trie<&str> = Trie::new(); + trie.insert("/café/menü", "unicode"); + assert_eq!(trie.search("/café/menü"), Some(&"unicode")); + } + + #[test] + fn test_trie_many_entries() { + let mut trie: Trie = Trie::new(); + for i in 0..1000 { + trie.insert(&format!("/path/{}", i), i); + } + assert_eq!(trie.search("/path/0"), Some(&0)); + assert_eq!(trie.search("/path/999"), Some(&999)); + assert!(trie.search("/path/1000").is_none()); + } + + #[test] + fn test_trie_default() { + let trie: Trie = Trie::default(); + assert!(trie.root.children.is_empty()); + assert!(!trie.match_all); + } + + #[test] + fn test_trie_shared_prefix_insert() { + let mut trie: Trie = Trie::new(); + for i in 0..100 { + trie.insert(&format!("/api/v1/resource/{}", i), i); + } + for i in 0..100 { + assert_eq!( + trie.search(&format!("/api/v1/resource/{}", i)), + Some(&i), + "shared prefix path {} not found", + i + ); + } + // Intermediate nodes should not have values + assert!(trie.search("/api").is_none()); + assert!(trie.search("/api/v1").is_none()); + assert!(trie.search("/api/v1/resource").is_none()); + } + + #[test] + fn test_trie_overwrite_preserves_others() { + let mut trie: Trie = Trie::new(); + trie.insert("/a/b/c", 1); + trie.insert("/a/b/d", 2); + trie.insert("/a/b/e", 3); + // Overwrite /a/b/c + trie.insert("/a/b/c", 99); + + assert_eq!(trie.search("/a/b/c"), Some(&99)); + assert_eq!(trie.search("/a/b/d"), Some(&2)); + assert_eq!(trie.search("/a/b/e"), Some(&3)); + } + + #[test] + fn test_trie_insert_search_full_urls() { + let mut trie: Trie<&str> = Trie::new(); + // Different leaf segments so they don't overwrite each other + trie.insert("https://example.com/users/profile", "profile"); + trie.insert("/users/settings", "settings"); + trie.insert("http://other.com/api/data", "data"); + + // Full URL and bare path resolve to same trie node (host stripped) + assert_eq!( + trie.search("https://example.com/users/profile"), + Some(&"profile") + ); + assert_eq!(trie.search("/users/profile"), Some(&"profile")); + assert_eq!( + trie.search("https://any.com/users/settings"), + Some(&"settings") + ); + assert_eq!(trie.search("/api/data"), Some(&"data")); + assert_eq!( + trie.search("http://cdn.example.com/api/data"), + Some(&"data") + ); + // Non-existent path + assert!(trie.search("/users/unknown").is_none()); + } +} diff --git a/spider/src/utils/uring_fs.rs b/spider/src/utils/uring_fs.rs new file mode 100644 index 000000000..9c57d1170 --- /dev/null +++ b/spider/src/utils/uring_fs.rs @@ -0,0 +1,853 @@ +//! Async file I/O with optional io_uring acceleration. +//! +//! On Linux with the `io_uring` feature, file operations are dispatched to a +//! dedicated worker thread that drives a raw io_uring ring for true +//! kernel-async I/O. On all other platforms (or when io_uring is unavailable +//! at runtime — e.g. seccomp-filtered containers, older kernels), operations +//! transparently fall back to `tokio::fs`. +//! +//! **No mutexes. No async runtime on the worker.** The worker thread runs a +//! tight synchronous loop: `blocking_recv` → io_uring submit → reap CQE → +//! send result back via oneshot. + +use std::io; +use tokio::sync::{mpsc, oneshot}; + +/// Internal operation sent to a streaming writer's background task. +enum StreamOp { + /// Write a chunk at the current offset. + Write(Vec, oneshot::Sender>), + /// Close the file and send the result. + Close(oneshot::Sender>), +} + +// ── io_uring implementation (raw io-uring crate) ──────────────────────────── + +#[cfg(all(target_os = "linux", feature = "io_uring"))] +mod inner { + use std::ffi::CString; + use std::io; + use std::sync::atomic::{AtomicBool, Ordering}; + use tokio::sync::{mpsc, oneshot}; + + /// Whether the io_uring FS worker is running and healthy. + static URING_FS_ENABLED: AtomicBool = AtomicBool::new(false); + + /// Channel to the io_uring worker thread. + /// `mpsc::UnboundedSender` is `Send + Sync`, safe for `OnceLock`. + static URING_FS_POOL: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + /// A self-contained file I/O task sent to the worker thread. + enum FileIoTask { + WriteFile { + path: String, + data: Vec, + tx: oneshot::Sender>, + }, + ReadFile { + path: String, + tx: oneshot::Sender>>, + }, + RemoveFile { + path: String, + tx: oneshot::Sender>, + }, + CreateDirAll { + path: String, + tx: oneshot::Sender>, + }, + } + + // ── Kernel probe ──────────────────────────────────────────────────────── + + /// Try to create a minimal io_uring ring. If the kernel or seccomp policy + /// blocks it (ENOSYS, EPERM, etc.), returns `None`. + pub(crate) fn probe_io_uring() -> Option { + match io_uring::IoUring::builder().build(64) { + Ok(ring) => { + log::info!("io_uring probe succeeded — kernel supports io_uring"); + Some(ring) + } + Err(e) => { + log::info!( + "io_uring unavailable ({}), file I/O will use tokio::fs fallback", + e + ); + None + } + } + } + + // ── Worker loop (synchronous, no async runtime) ───────────────────────── + + /// The worker thread's main loop. Receives tasks via blocking channel recv, + /// processes each through the io_uring ring, sends results back via oneshot. + /// Exits cleanly when the channel sender is dropped. + fn worker_loop(mut rx: mpsc::UnboundedReceiver, mut ring: io_uring::IoUring) { + while let Some(task) = rx.blocking_recv() { + match task { + FileIoTask::WriteFile { path, data, tx } => { + let _ = tx.send(uring_write_file(&mut ring, &path, &data)); + } + FileIoTask::ReadFile { path, tx } => { + let _ = tx.send(uring_read_file(&mut ring, &path)); + } + FileIoTask::RemoveFile { path, tx } => { + // unlinkat not widely supported in io_uring 0.7 — use std + let _ = tx.send(std::fs::remove_file(&path)); + } + FileIoTask::CreateDirAll { path, tx } => { + // mkdir doesn't benefit from io_uring + let _ = tx.send(std::fs::create_dir_all(&path)); + } + } + } + // Channel closed — drop ring (closes the io_uring fd) + drop(ring); + } + + // ── io_uring file operations ──────────────────────────────────────────── + + /// Submit one SQE, wait for one CQE, return the result code. + fn submit_and_reap(ring: &mut io_uring::IoUring) -> io::Result { + ring.submit_and_wait(1)?; + let cqe = ring + .completion() + .next() + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "io_uring: no CQE after wait"))?; + Ok(cqe.result()) + } + + /// Close an fd through io_uring. Best-effort — errors are returned but + /// the fd is consumed regardless. + fn uring_close(ring: &mut io_uring::IoUring, fd: i32) -> io::Result<()> { + let close_e = io_uring::opcode::Close::new(io_uring::types::Fd(fd)) + .build() + .user_data(0xC105E); + + // SAFETY: SQE references only the fd integer, no buffer pointers. + unsafe { + ring.submission() + .push(&close_e) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "io_uring: SQ full on close"))?; + } + + let res = submit_and_reap(ring)?; + if res < 0 { + return Err(io::Error::from_raw_os_error(-res)); + } + Ok(()) + } + + /// Write `data` to `path` (create/truncate) using io_uring OpenAt → Write + /// loop → Close. + fn uring_write_file(ring: &mut io_uring::IoUring, path: &str, data: &[u8]) -> io::Result<()> { + let c_path = + CString::new(path).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + + // ── Open (O_WRONLY | O_CREAT | O_TRUNC) ──────────────────────────── + let open_e = + io_uring::opcode::OpenAt::new(io_uring::types::Fd(libc::AT_FDCWD), c_path.as_ptr()) + .flags(libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC) + .mode(0o644) + .build() + .user_data(0x0BE4); + + // SAFETY: c_path is alive and pinned on the stack until after + // submit_and_reap returns. + unsafe { + ring.submission() + .push(&open_e) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "io_uring: SQ full on open"))?; + } + + let fd = submit_and_reap(ring)?; + if fd < 0 { + return Err(io::Error::from_raw_os_error(-fd)); + } + + // ── Write (loop for short writes) ─────────────────────────────────── + let write_result = uring_write_all(ring, fd, data); + + // ── Close (always, even on write error) ───────────────────────────── + let close_result = uring_close(ring, fd); + + // Return first error encountered. + write_result?; + close_result + } + + /// Write all of `data` to `fd` at offset 0, handling short writes. + fn uring_write_all(ring: &mut io_uring::IoUring, fd: i32, data: &[u8]) -> io::Result<()> { + if data.is_empty() { + return Ok(()); + } + + let mut offset: u64 = 0; + while (offset as usize) < data.len() { + let remaining = &data[offset as usize..]; + // io_uring Write len is u32 — cap each submission. + let chunk_len = remaining.len().min(u32::MAX as usize) as u32; + + let write_e = io_uring::opcode::Write::new( + io_uring::types::Fd(fd), + remaining.as_ptr(), + chunk_len, + ) + .offset(offset) + .build() + .user_data(0x1417E); + + // SAFETY: `remaining` (a slice of `data`) is alive on the stack + // and won't move before submit_and_reap returns. + unsafe { + ring.submission().push(&write_e).map_err(|_| { + io::Error::new(io::ErrorKind::Other, "io_uring: SQ full on write") + })?; + } + + let written = submit_and_reap(ring)?; + if written < 0 { + return Err(io::Error::from_raw_os_error(-written)); + } + if written == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "io_uring: write returned 0 bytes", + )); + } + offset += written as u64; + } + Ok(()) + } + + /// Read the entire file at `path` using io_uring OpenAt → Read loop → Close. + fn uring_read_file(ring: &mut io_uring::IoUring, path: &str) -> io::Result> { + // Get file size via std::fs (fast, synchronous stat). + let meta = std::fs::metadata(path)?; + let len = meta.len() as usize; + + if len == 0 { + return Ok(Vec::new()); + } + + let c_path = + CString::new(path).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + + // ── Open (O_RDONLY) ───────────────────────────────────────────────── + let open_e = + io_uring::opcode::OpenAt::new(io_uring::types::Fd(libc::AT_FDCWD), c_path.as_ptr()) + .flags(libc::O_RDONLY) + .build() + .user_data(0x0BE4); + + // SAFETY: c_path alive until after submit_and_reap. + unsafe { + ring.submission() + .push(&open_e) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "io_uring: SQ full on open"))?; + } + + let fd = submit_and_reap(ring)?; + if fd < 0 { + return Err(io::Error::from_raw_os_error(-fd)); + } + + // ── Read (loop for short reads) ───────────────────────────────────── + let mut buf = vec![0u8; len]; + let read_result = uring_read_exact(ring, fd, &mut buf); + + // ── Close ─────────────────────────────────────────────────────────── + let close_result = uring_close(ring, fd); + + read_result?; + close_result?; + Ok(buf) + } + + /// Read exactly `buf.len()` bytes from `fd` at offset 0, handling short reads. + fn uring_read_exact(ring: &mut io_uring::IoUring, fd: i32, buf: &mut [u8]) -> io::Result<()> { + let mut offset: u64 = 0; + while (offset as usize) < buf.len() { + let remaining = &mut buf[offset as usize..]; + let chunk_len = remaining.len().min(u32::MAX as usize) as u32; + + let read_e = io_uring::opcode::Read::new( + io_uring::types::Fd(fd), + remaining.as_mut_ptr(), + chunk_len, + ) + .offset(offset) + .build() + .user_data(0x4EAD); + + // SAFETY: `remaining` is a mutable slice of `buf` on the heap. + // It won't move before submit_and_reap returns. + unsafe { + ring.submission().push(&read_e).map_err(|_| { + io::Error::new(io::ErrorKind::Other, "io_uring: SQ full on read") + })?; + } + + let n = submit_and_reap(ring)?; + if n < 0 { + return Err(io::Error::from_raw_os_error(-n)); + } + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "io_uring: read returned 0 (unexpected EOF)", + )); + } + offset += n as u64; + } + Ok(()) + } + + // ── Public API ────────────────────────────────────────────────────────── + + /// Probe the kernel for io_uring support and, if available, spawn the + /// worker thread. Returns `true` when io_uring file I/O is active. + /// + /// Safe to call multiple times — only the first successful call has + /// effect. Subsequent calls return the cached status. + pub fn init_uring_fs() -> bool { + // Fast path: already initialized. + if URING_FS_ENABLED.load(Ordering::Acquire) { + return true; + } + + // Probe: try to create a ring. Fails gracefully on AWS AL2, ECS, + // Lambda, seccomp-filtered containers, kernels < 5.1, etc. + let ring = match probe_io_uring() { + Some(r) => r, + None => return false, + }; + + let (tx, rx) = mpsc::unbounded_channel(); + let builder = std::thread::Builder::new().name("uring-fs-worker".into()); + + match builder.spawn(move || worker_loop(rx, ring)) { + Ok(_) => { + if URING_FS_POOL.set(tx).is_ok() { + URING_FS_ENABLED.store(true, Ordering::Release); + } + // If set() failed, another thread won the race. Our worker + // will exit when its rx is dropped — no leak. + } + Err(e) => { + log::warn!("Failed to spawn io_uring FS worker thread: {}", e); + return false; + } + } + + URING_FS_ENABLED.load(Ordering::Acquire) + } + + /// Send a task to the io_uring worker and await the result. + /// Returns `None` if io_uring is not available (caller falls back). + async fn try_uring( + make_task: impl FnOnce(oneshot::Sender>) -> FileIoTask, + ) -> Option> { + if !URING_FS_ENABLED.load(Ordering::Acquire) { + return None; + } + let sender = URING_FS_POOL.get()?; + let (tx, rx) = oneshot::channel(); + if sender.send(make_task(tx)).is_err() { + return Some(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "io_uring FS worker channel closed", + ))); + } + match rx.await { + Ok(result) => Some(result), + Err(_) => Some(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "io_uring FS worker dropped the response", + ))), + } + } + + /// Streaming writes always use the tokio::fs fallback. + /// io_uring doesn't add value for sequential single-writer streaming. + pub(super) async fn try_streaming_create( + _path: String, + ) -> Option>> { + None + } + + /// Write `data` to `path`, creating or truncating the file. + pub async fn write_file(path: String, data: Vec) -> io::Result<()> { + if let Some(result) = try_uring(|tx| FileIoTask::WriteFile { + path: path.clone(), + data: data.clone(), + tx, + }) + .await + { + return result; + } + tokio::fs::write(&path, &data).await + } + + /// Read the entire contents of `path` into a `Vec`. + pub async fn read_file(path: String) -> io::Result> { + if let Some(result) = try_uring(|tx| FileIoTask::ReadFile { + path: path.clone(), + tx, + }) + .await + { + return result; + } + tokio::fs::read(&path).await + } + + /// Remove a file at `path`. + pub async fn remove_file(path: String) -> io::Result<()> { + if let Some(result) = try_uring(|tx| FileIoTask::RemoveFile { + path: path.clone(), + tx, + }) + .await + { + return result; + } + tokio::fs::remove_file(&path).await + } + + /// Recursively create directories at `path`. + pub async fn create_dir_all(path: String) -> io::Result<()> { + if let Some(result) = try_uring(|tx| FileIoTask::CreateDirAll { + path: path.clone(), + tx, + }) + .await + { + return result; + } + tokio::fs::create_dir_all(&path).await + } +} + +// ── Fallback implementation (non-Linux or no io_uring feature) ─────────────── + +#[cfg(not(all(target_os = "linux", feature = "io_uring")))] +mod inner { + use std::io; + use tokio::sync::mpsc; + + /// No-op on platforms without io_uring. Always returns `false`. + pub fn init_uring_fs() -> bool { + false + } + + /// No io_uring available — always returns `None`. + pub(super) async fn try_streaming_create( + _path: String, + ) -> Option>> { + None + } + + /// Write `data` to `path`, creating or truncating the file. + pub async fn write_file(path: String, data: Vec) -> io::Result<()> { + tokio::fs::write(&path, &data).await + } + + /// Read the entire contents of `path` into a `Vec`. + pub async fn read_file(path: String) -> io::Result> { + tokio::fs::read(&path).await + } + + /// Remove a file at `path`. + pub async fn remove_file(path: String) -> io::Result<()> { + tokio::fs::remove_file(&path).await + } + + /// Recursively create directories at `path`. + pub async fn create_dir_all(path: String) -> io::Result<()> { + tokio::fs::create_dir_all(&path).await + } +} + +// ── Re-exports ─────────────────────────────────────────────────────────────── + +pub use inner::create_dir_all; +pub use inner::init_uring_fs; +pub use inner::read_file; +pub use inner::remove_file; +pub use inner::write_file; + +// ── StreamingWriter ────────────────────────────────────────────────────────── + +/// A handle for streaming writes to a file. Writes are dispatched to a +/// background task — always via a spawned tokio task (io_uring doesn't help +/// sequential single-writer streaming). The file is created on [`create`] +/// and must be finalized with [`close`]. +/// +/// If the writer is dropped without calling [`close`], the background task +/// will still close the file (but the caller cannot observe errors). +pub struct StreamingWriter { + ops_tx: mpsc::UnboundedSender, +} + +impl StreamingWriter { + /// Create a new file at `path` for streaming writes. + pub async fn create(path: String) -> io::Result { + // Try io_uring path first (currently always returns None — + // streaming doesn't benefit from io_uring). + if let Some(result) = inner::try_streaming_create(path.clone()).await { + return result.map(|ops_tx| Self { ops_tx }); + } + // Fallback: tokio task + Self::create_fallback(path).await + } + + /// Fallback: spawn a tokio task that holds a `tokio::fs::File`. + async fn create_fallback(path: String) -> io::Result { + let file = tokio::fs::File::create(&path).await?; + let (ops_tx, mut ops_rx) = mpsc::unbounded_channel(); + + tokio::spawn(async move { + use tokio::io::AsyncWriteExt; + let mut file = file; + let mut close_tx: Option>> = None; + + while let Some(op) = ops_rx.recv().await { + match op { + StreamOp::Write(data, tx) => { + let _ = tx.send(file.write_all(&data).await); + } + StreamOp::Close(tx) => { + close_tx = Some(tx); + break; + } + } + } + + let result = file.flush().await; + if let Some(tx) = close_tx { + let _ = tx.send(result); + } + // file dropped — OS closes the fd + }); + + Ok(Self { ops_tx }) + } + + /// Write a chunk of data at the current offset. + pub async fn write(&self, data: &[u8]) -> io::Result<()> { + let (tx, rx) = oneshot::channel(); + self.ops_tx + .send(StreamOp::Write(data.to_vec(), tx)) + .map_err(|_| { + io::Error::new(io::ErrorKind::BrokenPipe, "streaming writer task exited") + })?; + rx.await.map_err(|_| { + io::Error::new( + io::ErrorKind::BrokenPipe, + "streaming writer dropped the response", + ) + })? + } + + /// Close the file and wait for completion. + pub async fn close(self) -> io::Result<()> { + let (tx, rx) = oneshot::channel(); + let _ = self.ops_tx.send(StreamOp::Close(tx)); + rx.await.unwrap_or(Ok(())) + } +} + +// ── Tests ──────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + fn temp_path(name: &str) -> String { + let dir = std::env::temp_dir(); + dir.join(format!("spider_uring_fs_test_{}", name)) + .display() + .to_string() + } + + // ── Fallback path tests (run on all platforms) ─────────────────────── + + #[tokio::test] + async fn test_write_read_remove_fallback() { + let path = temp_path("fallback"); + let payload = b"hello uring_fs fallback".to_vec(); + + write_file(path.clone(), payload.clone()).await.unwrap(); + + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back, payload); + + remove_file(path.clone()).await.unwrap(); + assert!(read_file(path).await.is_err()); + } + + #[tokio::test] + async fn test_fallback_when_not_initialized() { + // Without calling init_uring_fs(), should still work via tokio::fs + let path = temp_path("no_init"); + let payload = b"no init test".to_vec(); + + write_file(path.clone(), payload.clone()).await.unwrap(); + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back, payload); + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_write_empty_file() { + let path = temp_path("empty"); + + write_file(path.clone(), Vec::new()).await.unwrap(); + + let read_back = read_file(path.clone()).await.unwrap(); + assert!(read_back.is_empty()); + + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_read_nonexistent() { + let path = temp_path("nonexistent_surely"); + let result = read_file(path).await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_streaming_writer_fallback() { + let path = temp_path("streaming_fallback"); + + let writer = StreamingWriter::create(path.clone()).await.unwrap(); + writer.write(b"chunk1").await.unwrap(); + writer.write(b"chunk2").await.unwrap(); + writer.write(b"chunk3").await.unwrap(); + writer.close().await.unwrap(); + + let content = read_file(path.clone()).await.unwrap(); + assert_eq!(content, b"chunk1chunk2chunk3"); + + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_streaming_writer_drop_without_close() { + let path = temp_path("streaming_drop"); + + let writer = StreamingWriter::create(path.clone()).await.unwrap(); + writer.write(b"data before drop").await.unwrap(); + drop(writer); + + // Give the background task a moment to close + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + + let content = read_file(path.clone()).await.unwrap(); + assert_eq!(content, b"data before drop"); + + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_create_dir_all_fallback() { + let base = temp_path("dir_test"); + let nested = format!("{}/a/b/c", base); + + create_dir_all(nested.clone()).await.unwrap(); + assert!(std::path::Path::new(&nested).is_dir()); + + // Cleanup + let _ = std::fs::remove_dir_all(&base); + } + + #[tokio::test] + async fn test_init_idempotent() { + // Calling init multiple times must not panic or deadlock. + let r1 = init_uring_fs(); + let r2 = init_uring_fs(); + // Both should return the same value (platform-dependent). + assert_eq!(r1, r2); + } + + #[tokio::test] + async fn test_large_write_read() { + let path = temp_path("large_file"); + // 1 MB payload — tests short-write loop handling. + let payload = vec![0xABu8; 1024 * 1024]; + + write_file(path.clone(), payload.clone()).await.unwrap(); + + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back.len(), payload.len()); + assert_eq!(read_back, payload); + + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_concurrent_writes() { + // Spawn 8 concurrent write+read tasks. No deadlocks, no data + // corruption. + let mut handles = Vec::new(); + + for i in 0..8u32 { + let path = temp_path(&format!("concurrent_{}", i)); + handles.push(tokio::spawn(async move { + let payload = vec![i as u8; 4096]; + write_file(path.clone(), payload.clone()).await.unwrap(); + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back, payload); + let _ = remove_file(path).await; + })); + } + + for h in handles { + h.await.unwrap(); + } + } + + #[tokio::test] + async fn test_overwrite_existing_file() { + let path = temp_path("overwrite"); + + write_file(path.clone(), b"first content".to_vec()) + .await + .unwrap(); + write_file(path.clone(), b"second".to_vec()).await.unwrap(); + + let content = read_file(path.clone()).await.unwrap(); + assert_eq!(content, b"second"); + + let _ = remove_file(path).await; + } + + #[tokio::test] + async fn test_remove_nonexistent() { + let path = temp_path("remove_nonexistent_surely"); + let result = remove_file(path).await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_streaming_writer_large_payload() { + let path = temp_path("streaming_large"); + + let writer = StreamingWriter::create(path.clone()).await.unwrap(); + let chunk = vec![0xCDu8; 4096]; + for _ in 0..64 { + writer.write(&chunk).await.unwrap(); + } + writer.close().await.unwrap(); + + let content = read_file(path.clone()).await.unwrap(); + assert_eq!(content.len(), 4096 * 64); + assert!(content.iter().all(|&b| b == 0xCD)); + + let _ = remove_file(path).await; + } + + // ── io_uring-specific tests (Linux + feature only) ────────────────── + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_write_read_remove_uring() { + let _ = init_uring_fs(); + let path = temp_path("uring_raw"); + let payload = vec![0xABu8; 4096]; // 4 KB + + write_file(path.clone(), payload.clone()).await.unwrap(); + + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back, payload); + + remove_file(path.clone()).await.unwrap(); + assert!(read_file(path).await.is_err()); + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_uring_large_write_read() { + let _ = init_uring_fs(); + let path = temp_path("uring_large"); + // 2 MB — exercises short-write loop through io_uring. + let payload = vec![0xFEu8; 2 * 1024 * 1024]; + + write_file(path.clone(), payload.clone()).await.unwrap(); + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back.len(), payload.len()); + assert_eq!(read_back, payload); + + let _ = remove_file(path).await; + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_uring_concurrent_ops() { + let _ = init_uring_fs(); + let mut handles = Vec::new(); + + for i in 0..16u32 { + let path = temp_path(&format!("uring_conc_{}", i)); + handles.push(tokio::spawn(async move { + let payload = vec![i as u8; 8192]; + write_file(path.clone(), payload.clone()).await.unwrap(); + let read_back = read_file(path.clone()).await.unwrap(); + assert_eq!(read_back, payload); + let _ = remove_file(path).await; + })); + } + + for h in handles { + h.await.unwrap(); + } + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_uring_empty_file() { + let _ = init_uring_fs(); + let path = temp_path("uring_empty"); + + write_file(path.clone(), Vec::new()).await.unwrap(); + let read_back = read_file(path.clone()).await.unwrap(); + assert!(read_back.is_empty()); + + let _ = remove_file(path).await; + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_uring_overwrite() { + let _ = init_uring_fs(); + let path = temp_path("uring_overwrite"); + + write_file(path.clone(), b"original data here".to_vec()) + .await + .unwrap(); + write_file(path.clone(), b"replaced".to_vec()) + .await + .unwrap(); + + let content = read_file(path.clone()).await.unwrap(); + assert_eq!(content, b"replaced"); + + let _ = remove_file(path).await; + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + #[tokio::test] + async fn test_probe_io_uring_does_not_panic() { + // probe_io_uring must never panic — returns None on unsupported + // kernels, Some on supported ones. + let result = super::inner::probe_io_uring(); + // We just verify it didn't panic. The result depends on the + // kernel, so we don't assert a specific value. + drop(result); + } +} diff --git a/spider/src/utils/validation.rs b/spider/src/utils/validation.rs new file mode 100644 index 000000000..07a16b3a1 --- /dev/null +++ b/spider/src/utils/validation.rs @@ -0,0 +1,367 @@ +use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind}; +use lazy_static::lazy_static; + +/// Scan only the first bytes (fast + bounded). +pub const PREFIX_SCAN: usize = 2048; + +/// If the HTML is huge, avoid loose fallback to reduce false positives. +pub const MAX_LEN_FOR_LOOSE_FALLBACK: usize = 8 * 1024; + +/// DataDome tail signature (exact end match after trimming ASCII whitespace). +const DATADOME_END: &[u8] = br#"title="DataDome Device Check">"#; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] +enum Lang { + En = 0, + Es = 1, + Fr = 2, + De = 3, + Pt = 4, + It = 5, + Nl = 6, + Ru = 7, +} + +#[derive(Copy, Clone, Debug)] +enum PatKind { + /// Html page. + Html, + /// Cloudflare-style "checking your browser..." + CheckingBrowser, + Lang(Lang), + /// Strong, highly-specific markers (tagged). + TaggedBlock(Lang), + /// Loose markers (words/phrases). Requires ALSO seeing a 403 marker. + WordBlock(Lang), + /// 403 marker (generic). + Code403, +} + +/// Normalize "en-US", "EN_us", "fr-FR" -> Lang (defaults to En). +#[inline] +fn normalize_lang_hint(lang_hint: Option<&str>) -> Lang { + let s = lang_hint.unwrap_or("en").trim(); + if s.is_empty() { + return Lang::En; + } + let s = s.as_bytes(); + let a = s.first().copied().unwrap_or(b'e').to_ascii_lowercase(); + let b = s.get(1).copied().unwrap_or(b'n').to_ascii_lowercase(); + match (a, b) { + (b'e', b'n') => Lang::En, + (b'e', b's') => Lang::Es, + (b'f', b'r') => Lang::Fr, + (b'd', b'e') => Lang::De, + (b'p', b't') => Lang::Pt, + (b'i', b't') => Lang::It, + (b'n', b'l') => Lang::Nl, + (b'r', b'u') => Lang::Ru, + _ => Lang::En, + } +} + +#[inline] +fn lang_bit(l: Lang) -> u16 { + 1u16 << (l as u8) +} + +#[inline] +fn trim_ascii_end(mut b: &[u8]) -> &[u8] { + while let Some(&last) = b.last() { + if last.is_ascii_whitespace() { + b = &b[..b.len() - 1]; + } else { + break; + } + } + b +} + +#[inline] +fn ends_with_datadome_device_check(content: Option<&[u8]>) -> bool { + let bytes = match content { + Some(b) if !b.is_empty() => b, + _ => return false, + }; + let tail = trim_ascii_end(bytes); + tail.len() >= DATADOME_END.len() && tail.ends_with(DATADOME_END) +} + +lazy_static! { + /// Single source of truth so patterns + kinds can’t drift. + static ref FALSE_403_RULES: Vec<(&'static str, PatKind)> = { + let mut r: Vec<(&'static str, PatKind)> = Vec::new(); + + // Basic HTML presence gate + r.push(("403", PatKind::Code403), // catches "403 Prohibido" etc + ("<h1>403</h1>", PatKind::Code403), // requested + (">403<", PatKind::Code403), // catches <h1 ...>403</h1> too + (" 403 ", PatKind::Code403), // broad fallback + ("\n403\n", PatKind::Code403), // broad fallback + ]); + + // Strong/tagged 403/access-denied pages (language-specific) + r.extend([ + // EN + ("<title>403 forbidden", PatKind::TaggedBlock(Lang::En)), + ("

forbidden

", PatKind::TaggedBlock(Lang::En)), + ("

403 forbidden

", PatKind::TaggedBlock(Lang::En)), + ("access denied", PatKind::TaggedBlock(Lang::En)), + ("

access denied

", PatKind::TaggedBlock(Lang::En)), + + // ES + ("403 prohibido", PatKind::TaggedBlock(Lang::Es)), + ("

prohibido

", PatKind::TaggedBlock(Lang::Es)), + ("acceso denegado", PatKind::TaggedBlock(Lang::Es)), + ("

acceso denegado

", PatKind::TaggedBlock(Lang::Es)), + + // FR + ("403 interdit", PatKind::TaggedBlock(Lang::Fr)), + ("

interdit

", PatKind::TaggedBlock(Lang::Fr)), + ("acces interdit", PatKind::TaggedBlock(Lang::Fr)), + ("accès interdit", PatKind::TaggedBlock(Lang::Fr)), + ("

acces interdit

", PatKind::TaggedBlock(Lang::Fr)), + ("

accès interdit

", PatKind::TaggedBlock(Lang::Fr)), + + // DE + ("403 verboten", PatKind::TaggedBlock(Lang::De)), + ("

verboten

", PatKind::TaggedBlock(Lang::De)), + ("zugriff verweigert", PatKind::TaggedBlock(Lang::De)), + ("

zugriff verweigert

", PatKind::TaggedBlock(Lang::De)), + + // PT + ("403 proibido", PatKind::TaggedBlock(Lang::Pt)), + ("

proibido

", PatKind::TaggedBlock(Lang::Pt)), + ("acesso negado", PatKind::TaggedBlock(Lang::Pt)), + ("

acesso negado

", PatKind::TaggedBlock(Lang::Pt)), + + // IT + ("403 vietato", PatKind::TaggedBlock(Lang::It)), + ("

vietato

", PatKind::TaggedBlock(Lang::It)), + ("accesso negato", PatKind::TaggedBlock(Lang::It)), + ("

accesso negato

", PatKind::TaggedBlock(Lang::It)), + + // NL + ("403 verboden", PatKind::TaggedBlock(Lang::Nl)), + ("

verboden

", PatKind::TaggedBlock(Lang::Nl)), + + // RU + ("доступ запрещен", PatKind::TaggedBlock(Lang::Ru)), + ("

доступ запрещен

", PatKind::TaggedBlock(Lang::Ru)), + ("запрещено", PatKind::TaggedBlock(Lang::Ru)), + ("

запрещено

", PatKind::TaggedBlock(Lang::Ru)), + ]); + + r.extend([ + ("radware bot manager captcha", PatKind::TaggedBlock(Lang::En)), + ("radware bot manager captcha", PatKind::TaggedBlock(Lang::En)), + ("cdn.perfdrive.com/aperture/aperture.js", PatKind::TaggedBlock(Lang::En)), + ("captcha.perfdrive.com/captcha-public/", PatKind::TaggedBlock(Lang::En)), + ("validate.perfdrive.com", PatKind::TaggedBlock(Lang::En)), + ]); + + r.extend([ + // EN + ("forbidden", PatKind::WordBlock(Lang::En)), + ("access denied", PatKind::WordBlock(Lang::En)), + ("access to this resource on the server is denied", PatKind::WordBlock(Lang::En)), + + // ES + ("prohibido", PatKind::WordBlock(Lang::Es)), + ("acceso denegado", PatKind::WordBlock(Lang::Es)), + + // FR + ("interdit", PatKind::WordBlock(Lang::Fr)), + ("acces interdit", PatKind::WordBlock(Lang::Fr)), + ("accès interdit", PatKind::WordBlock(Lang::Fr)), + + // DE + ("verboten", PatKind::WordBlock(Lang::De)), + ("zugriff verweigert", PatKind::WordBlock(Lang::De)), + + // PT + ("proibido", PatKind::WordBlock(Lang::Pt)), + ("acesso negado", PatKind::WordBlock(Lang::Pt)), + + // IT + ("vietato", PatKind::WordBlock(Lang::It)), + ("accesso negato", PatKind::WordBlock(Lang::It)), + + // NL + ("verboden", PatKind::WordBlock(Lang::Nl)), + + // RU + ("доступ запрещен", PatKind::WordBlock(Lang::Ru)), + ("запрещено", PatKind::WordBlock(Lang::Ru)), + ("запрещен", PatKind::WordBlock(Lang::Ru)), + ]); + + r + }; + + static ref FALSE_403_PATTERNS: Vec<&'static str> = FALSE_403_RULES.iter().map(|(p, _)| *p).collect(); + static ref FALSE_403_KINDS: Vec = FALSE_403_RULES.iter().map(|(_, k)| *k).collect(); + + static ref FALSE_403_AC: AhoCorasick = AhoCorasickBuilder::new() + .ascii_case_insensitive(true) + .match_kind(MatchKind::LeftmostLongest) // prefer longer matches at same start + .build(FALSE_403_PATTERNS.as_slice()) + .expect("FALSE_403_AC build"); +} + +/// True if the body looks like a “false success” block page. +/// +/// - DataDome: exact end signature (fast `ends_with`) +/// - Cloudflare challenge: "checking your browser..." in prefix +/// - 403/access denied pages: +/// - strong tagged match OR +/// - (Code403 + word match) fallback (disabled if body > 8k) +#[inline] +pub fn is_false_403(content: Option<&[u8]>, lang_hint: Option<&str>) -> bool { + if ends_with_datadome_device_check(content) { + return true; + } + + let bytes = match content { + Some(b) if !b.is_empty() => b, + _ => return false, + }; + + let head = &bytes[..bytes.len().min(PREFIX_SCAN)]; + + let mut has_html = false; + let mut has_checking = false; + let mut has_403 = false; + + let mut detected_lang: Option = None; + let mut tagged_hits: u16 = 0; + let mut word_hits: u16 = 0; + + for m in FALSE_403_AC.find_iter(head) { + let idx = m.pattern().as_usize(); + match FALSE_403_KINDS.get(idx).copied() { + Some(PatKind::Html) => has_html = true, + Some(PatKind::CheckingBrowser) => has_checking = true, + Some(PatKind::Code403) => has_403 = true, + Some(PatKind::Lang(l)) => { + if detected_lang.is_none() { + detected_lang = Some(l); + } + } + Some(PatKind::TaggedBlock(l)) => tagged_hits |= lang_bit(l), + Some(PatKind::WordBlock(l)) => word_hits |= lang_bit(l), + None => {} + } + + if has_html && (has_checking || tagged_hits != 0 || (has_403 && word_hits != 0)) { + break; + } + } + + if !has_html { + return false; + } + + if has_checking { + return true; + } + + let effective = detected_lang.unwrap_or_else(|| normalize_lang_hint(lang_hint)); + let lang_mask = lang_bit(effective) | lang_bit(Lang::En); + + // Strong tagged hit (includes Radware/perfdrive markers) + if (tagged_hits & lang_mask) != 0 { + return true; + } + + // Loose fallback only if body isn't huge (optional safety heuristic) + if bytes.len() > MAX_LEN_FOR_LOOSE_FALLBACK { + return false; + } + + has_403 && (word_hits & lang_mask) != 0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn false_for_empty_is_false() { + assert!(!is_false_403(None, None)); + assert!(!is_false_403(Some(b""), None)); + } + + #[test] + fn detects_checking_your_browser() { + let html = br#"Checking your browser...wait"#; + assert!(is_false_403(Some(html), None)); + } + + #[test] + fn detects_datadome_tail_end_match() { + let body = b"random... title=\"DataDome Device Check\">\n"; + assert!(is_false_403(Some(body), None)); + } + + #[test] + fn detects_en_title_403() { + let html = br#"403 Forbiddenno"#; + assert!(is_false_403(Some(html), None)); + } + + #[test] + fn detects_es_with_lang() { + let html = br#"403 Prohibido"#; + assert!(is_false_403(Some(html), Some("en"))); + } + + #[test] + fn detect_failed_403_generic() { + let body = br###" + + 403 Forbidden + +
+

403

+

Forbidden +

+

Access to this resource on the server is denied!

+
+"###; + assert!(is_false_403(Some(body), None)); + } + + #[test] + fn detects_radware_bot_manager_captcha_title() { + let body = br#"Radware Bot Manager Captcha"#; + assert!(is_false_403(Some(body), None)); + } + + #[test] + fn prefix_bound_does_not_match_if_beyond_prefix() { + let mut v = vec![b'a'; PREFIX_SCAN + 128]; + v.extend_from_slice(b"403 Forbidden"); + assert!(!is_false_403(Some(&v), None)); + } +} diff --git a/spider/src/website.rs b/spider/src/website.rs new file mode 100644 index 000000000..0b6651452 --- /dev/null +++ b/spider/src/website.rs @@ -0,0 +1,11638 @@ +use crate::black_list::contains; +use crate::client::redirect::Policy; +use crate::compact_str::CompactString; +use crate::configuration::{ + self, get_ua, AutomationScriptsMap, Configuration, ExecutionScriptsMap, RedirectPolicy, + SerializableHeaderMap, +}; +use crate::{page::build, utils::PageResponse}; + +use crate::features::chrome_common::RequestInterceptConfiguration; +#[cfg(feature = "disk")] +use crate::features::disk::DatabaseHandler; +use crate::packages::robotparser::parser::RobotFileParser; +use crate::page::{ + AntiBotTech, Page, PageLinkBuildSettings, CHROME_UNKNOWN_STATUS_ERROR, UNKNOWN_STATUS_ERROR, +}; +use crate::utils::abs::{convert_abs_url, parse_absolute_url}; +use crate::utils::interner::ListBucket; +use crate::utils::{ + crawl_duration_expired, emit_log, emit_log_shutdown, get_path_from_url, get_semaphore, + networking_capable, prepare_url, setup_website_selectors, spawn_set, AllowedDomainTypes, +}; + +/// Run a future with an optional hard wall-clock timeout. +/// When `timeout` is `None`, the future runs without any timer overhead. +/// When the timeout fires, the future is dropped (cancelling all in-flight work). +async fn run_with_crawl_timeout( + timeout: Option, + url: &str, + fut: impl core::future::Future, +) { + match timeout { + Some(t) => { + if tokio::time::timeout(t, fut).await.is_err() { + log::warn!("crawl hard timeout after {t:?} for {url}"); + } + } + None => fut.await, + } +} +use crate::{CaseInsensitiveString, Client, ClientBuilder, RelativeSelectors}; +#[cfg(feature = "cron")] +use async_job::{async_trait, Job, Runner}; +use hashbrown::{HashMap, HashSet}; +use reqwest::header::REFERER; +use reqwest::StatusCode; +use std::fmt; +use std::net::IpAddr; +use std::sync::atomic::{AtomicBool, AtomicI8, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::{ + sync::{broadcast, Semaphore}, + task::JoinSet, + time::Interval, +}; +use tokio_stream::StreamExt; +use url::Url; + +#[cfg(feature = "cache_request")] +use http_cache_reqwest::{Cache, CacheMode, HttpCache, HttpCacheOptions}; + +#[cfg(feature = "cache_request")] +pub use http_global_cache::CACACHE_MANAGER; + +/// The max backoff duration in seconds. +const BACKOFF_MAX_DURATION: tokio::time::Duration = tokio::time::Duration::from_secs(60); + +/// calculate the base limits +pub fn calc_limits(multiplier: usize) -> usize { + let logical = num_cpus::get(); + let physical = num_cpus::get_physical(); + + let sem_limit = if logical > physical { + (logical) / (physical) + } else { + logical + }; + + let (sem_limit, sem_max) = if logical == physical { + (sem_limit * physical, 30 * multiplier) + } else { + (sem_limit * 2, 20 * multiplier) + }; + + sem_limit.max(sem_max) +} + +/// Javascript challenge pages. +static JS_SAFE_CHALLENGE_PATTERNS: &[&str] = &[ + r#"Enable JavaScript and cookies to continue"#, // Cloudflare + r#"To continue, please enable JavaScript in your browser settings"#, // Akamai, F5 + r#"Please enable JavaScript to view the page content"#, // AWS WAF +]; + +/// check if the page is a javascript challenge +pub fn is_safe_javascript_challenge(page: &Page) -> bool { + let page = page.get_html_bytes_u8(); + + let page_size = page.len(); + + if page_size == 0 || page_size > 10_000 { + return false; + } + + AC_JS_CHALLENGE.find(page).is_some() +} + +#[cfg(all( + any( + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "solaris", + target_os = "tvos", + target_os = "visionos", + target_os = "watchos", + ), + any(not(feature = "wreq"), feature = "cache_request") +))] +/// Bind connections only on the specified network interface. +pub fn set_interface(client: ClientBuilder, network_interface: &str) -> ClientBuilder { + client.interface(network_interface) +} + +#[cfg(not(any( + all(feature = "wreq", not(feature = "cache_request")), + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "solaris", + target_os = "tvos", + target_os = "visionos", + target_os = "watchos", +)))] +/// Bind connections only on the specified network interface. +pub fn set_interface(client: ClientBuilder, _interface: &str) -> ClientBuilder { + client +} + +lazy_static! { + static ref AC_JS_CHALLENGE: aho_corasick::AhoCorasick = aho_corasick::AhoCorasick::new(JS_SAFE_CHALLENGE_PATTERNS).expect("safe challenges"); + /// The default Semaphore limits. + pub static ref DEFAULT_PERMITS: usize = calc_limits(1); + /// The shared global Semaphore. + pub(crate) static ref SEM_SHARED: Arc = { + let base_limit = match std::env::var("SEMAPHORE_MULTIPLIER") { + Ok(multiplier) => match multiplier.parse::() { + Ok(parsed_value) => (*DEFAULT_PERMITS as isize) + .wrapping_mul(parsed_value) + .max(1) as usize, + Err(_) => *DEFAULT_PERMITS, + }, + _ => *DEFAULT_PERMITS, + }; + Arc::new(Semaphore::const_new(base_limit)) + }; + /// The max links to store in memory. + pub(crate) static ref LINKS_VISITED_MEMORY_LIMIT: usize = { + const DEFAULT_LIMIT: usize = 15_000; + + match std::env::var("LINKS_VISITED_MEMORY_LIMIT") { + Ok(limit) => limit.parse::().unwrap_or(DEFAULT_LIMIT), + _ => DEFAULT_LIMIT + } + }; + static ref WILD_CARD_PATH: CaseInsensitiveString = CaseInsensitiveString::from("*"); +} + +#[cfg(not(feature = "decentralized"))] +lazy_static! { + /// The global Semaphore. + static ref SEM: Semaphore = { + let base_limit = calc_limits(1); + + let base_limit = match std::env::var("SEMAPHORE_MULTIPLIER") { + Ok(multiplier) => match multiplier.parse::() { + Ok(parsed_value) => (base_limit as isize * parsed_value).max(1) as usize, + Err(_) => base_limit, + }, + _ => base_limit, + }; + + Semaphore::const_new(base_limit) + }; +} + +#[cfg(feature = "decentralized")] +lazy_static! { + /// The global worker count. + static ref WORKERS: HashSet = { + let mut set: HashSet<_> = HashSet::new(); + + for worker in std::env::var("SPIDER_WORKER_SCRAPER") + .unwrap_or_else(|_| "http://127.0.0.1:3031".to_string()) + .split(",") + { + set.insert(worker.to_string()); + } + + for worker in std::env::var("SPIDER_WORKER") + .unwrap_or_else(|_| "http://127.0.0.1:3030".to_string()) + .split(",") + { + set.insert(worker.to_string()); + } + + set + }; + static ref SEM: Semaphore = { + let sem_limit = calc_limits(3); + Semaphore::const_new(sem_limit * WORKERS.len()) + }; +} + +// const INVALID_URL: &str = "The domain should be a valid URL, refer to ."; + +/// the active status of the crawl. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumString, strum::Display)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CrawlStatus { + /// The crawl did not start yet. + #[default] + Start, + /// The crawl is idle and has completed. + Idle, + /// The crawl is active. + Active, + /// The crawl blocked from network ratelimit, firewall, etc. + Blocked, + /// Crawl blocked from spider firewall. + FirewallBlocked, + /// The crawl failed from a server error. + ServerError, + /// The crawl failed from a connection error with proxy or dns. + ConnectError, + /// The crawl was rate limited. + RateLimited, + /// The initial request ran without returning html. + Empty, + /// The URL of the website is invalid. Crawl cannot commence. + Invalid, + #[cfg(feature = "control")] + /// The crawl shutdown manually. + Shutdown, + #[cfg(feature = "control")] + /// The crawl paused manually. + Paused, +} + +/// The link activity for the crawl. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumString, strum::Display)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ProcessLinkStatus { + /// The link can process. + #[default] + Allowed, + /// The link is blocked. + Blocked, + /// The budget is exceeded for the crawl. + BudgetExceeded, +} + +/// The type of cron job to run +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumString, strum::Display)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum CronType { + #[default] + /// Crawl collecting links, page data, and etc. + Crawl, + /// Scrape collecting links, page data as bytes to store, and etc. + Scrape, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, strum::EnumString, strum::Display)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// Generic website meta info for handling retries. +pub enum WebsiteMetaInfo { + /// The page requires Javascript. + RequiresJavascript, + /// Standard apache 403 page that requires a special http header for access like a custom iframe server. + Apache403, + /// Standard Open Resty 403 page that requires a special http header for access like a custom iframe server. + OpenResty403, + /// No meta info. + #[default] + None, +} + +/// On link find callback rewrite a url if it meets a condition. +pub type OnLinkFindCallback = Arc< + dyn Fn(CaseInsensitiveString, Option) -> (CaseInsensitiveString, Option) + + Send + + Sync, +>; + +/// Callback closure that determines if a link should be crawled or not. +pub trait OnShouldCrawlClosure: Fn(&Page) -> bool + Send + Sync + 'static {} +impl bool + Send + Sync + 'static> OnShouldCrawlClosure for F {} + +/// Callback closure or function pointer that determines if a link should be crawled or not. +#[derive(Clone)] +pub enum OnShouldCrawlCallback { + /// Static function pointer. + Fn(fn(&Page) -> bool), + + /// Closure. + Closure(Arc), +} +impl OnShouldCrawlCallback { + fn call(&self, page: &Page) -> bool { + match self { + Self::Fn(func) => func(page), + Self::Closure(closure) => closure(page), + } + } +} + +/// Round-robin client rotator for proxy rotation. +/// Each client is built with a single proxy, and `next()` cycles through them. +#[derive(Clone)] +pub struct ClientRotator { + clients: Vec, + index: Arc, +} + +impl ClientRotator { + /// Create a new rotator from a list of clients. + pub fn new(clients: Vec) -> Self { + Self { + clients, + index: Arc::new(AtomicUsize::new(0)), + } + } + + /// Get the next client in round-robin order. + pub fn next(&self) -> &Client { + let idx = self.index.fetch_add(1, Ordering::Relaxed) % self.clients.len(); + &self.clients[idx] + } + + /// Number of clients in the rotator. + pub fn len(&self) -> usize { + self.clients.len() + } + + /// Whether the rotator is empty. + pub fn is_empty(&self) -> bool { + self.clients.is_empty() + } + + /// Get two different clients for hedging. Returns (primary, hedge) where + /// hedge is guaranteed to be a different client if more than one exists. + #[cfg(feature = "hedge")] + pub fn next_pair(&self) -> (&Client, Option<&Client>) { + let len = self.clients.len(); + if len <= 1 { + return (&self.clients[0], None); + } + let idx = self.index.fetch_add(2, Ordering::Relaxed); + let primary_idx = idx % len; + let hedge_idx = (idx + 1) % len; + (&self.clients[primary_idx], Some(&self.clients[hedge_idx])) + } +} + +/// Represents a website to crawl and gather all links or page content. +/// ```rust +/// use spider::website::Website; +/// let mut website = Website::new("http://example.com"); +/// website.crawl(); +/// // `Website` will be filled with links or pages when crawled. If you need pages with the resource +/// // call the `website.scrape` method with `website.get_pages` instead. +/// for link in website.get_links() { +/// // do something +/// } +/// ``` +#[derive(Clone, Default)] +pub struct Website { + /// Configuration properties for website. + pub configuration: Box, + /// The callback when a link is found. + pub on_link_find_callback: Option, + /// The callback to use if a page should be ignored. Return false to ensure that the discovered links are not crawled. + pub on_should_crawl_callback: Option, + /// Set the crawl ID to track. This allows explicit targeting for shutdown, pause, and etc. + pub crawl_id: Box, + #[cfg(feature = "extra_information")] + /// Extra information to store. + pub extra_info: Option>, + /// Seed the initial html for crawling. + seed_html: Option, + /// All URLs visited. + links_visited: Box, + /// All signatures. + signatures: Box>, + /// Extra links to crawl. + extra_links: Box>, + /// Pages visited. + pages: Option>, + /// Robot.txt parser. + robot_file_parser: Option>, + /// Base url of the crawl. + url: Box, + /// The domain url parsed. + domain_parsed: Option>, + /// Subscribe and broadcast changes. + channel: Option<(broadcast::Sender, Arc>)>, + /// Guard counter for channel handling. This prevents things like the browser from closing after the crawl so that subscriptions can finalize events. + channel_guard: Option, + /// Send links to process during the crawl. + channel_queue: Option<(broadcast::Sender, Arc>)>, + /// The status of the active crawl this is mapped to a general status and not the HTTP status code. + status: CrawlStatus, + /// The initial status code of the first request. + initial_status_code: StatusCode, + /// The initial anti-bot tech found. + initial_anti_bot_tech: AntiBotTech, + /// The initial bytes size of the first request. + initial_html_length: usize, + /// The initial page had a waf detection. + initial_page_waf_check: bool, + /// The initial page should retry. + initial_page_should_retry: bool, + /// The website was manually stopped. + shutdown: bool, + /// The request client. Stored for re-use between runs. + client: Option, + /// Round-robin client rotator for proxy rotation. Built when 2+ proxies are configured. + client_rotator: Option>, + /// The disk handler to use. + #[cfg(feature = "disk")] + sqlite: Option>, + /// Configure sqlite on start + #[cfg(feature = "disk")] + enable_sqlite: bool, + /// Was the setup already configured for sync sendable thread use? + send_configured: bool, + /// The website requires javascript to load. This will be sent as a hint when http request. + website_meta_info: WebsiteMetaInfo, + /// Skip the initial link? + skip_initial: bool, + #[cfg(feature = "cookies")] + /// Cookie jar between request. + pub cookie_jar: Arc, +} + +impl fmt::Debug for Website { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let domain_str = self.domain_parsed.as_ref().map(|u| u.as_str().to_owned()); + let pages_len = self.pages.as_ref().map(|p| p.len()).unwrap_or(0); + + let mut ds = f.debug_struct("Website"); + + ds.field("url", &self.url.as_ref()) + .field("crawl_id", &self.crawl_id) + .field("domain_parsed", &domain_str) + // callbacks – just show presence, avoids Fn: Debug bound + .field( + "on_link_find_callback", + &self.on_link_find_callback.is_some(), + ) + .field( + "on_should_crawl_callback", + &self.on_should_crawl_callback.is_some(), + ) + // state + counters + .field("status", &self.status) + .field("shutdown", &self.shutdown) + .field("extra_links_len", &self.extra_links.len()) + .field("signatures_len", &self.signatures.len()) + .field("pages_len", &pages_len) + // channels / sqlite / client: just booleans + .field("channel_present", &self.channel.is_some()) + .field("channel_queue_present", &self.channel_queue.is_some()) + .field("client_present", &self.client.is_some()) + // initial page info + .field("initial_status_code", &self.initial_status_code) + .field("initial_html_length", &self.initial_html_length) + .field("initial_anti_bot_tech", &self.initial_anti_bot_tech) + .field("initial_page_waf_check", &self.initial_page_waf_check) + .field("initial_page_should_retry", &self.initial_page_should_retry) + // misc flags/meta + .field("send_configured", &self.send_configured) + .field("website_meta_info", &self.website_meta_info) + .field("skip_initial", &self.skip_initial); + + #[cfg(feature = "disk")] + { + ds.field("sqlite_present", &self.sqlite.is_some()) + .field("enable_sqlite", &self.enable_sqlite); + } + + ds.finish() + } +} + +impl Website { + /// Initialize the Website with a starting link to crawl and check the firewall base. + fn _new(url: &str, check_firewall: bool) -> Self { + let url = url.trim(); + let url: Box = if networking_capable(url) { + CaseInsensitiveString::new(&url).into() + } else { + CaseInsensitiveString::new(&prepare_url(url)).into() + }; + + let domain_parsed: Option> = parse_absolute_url(&url); + let mut status = CrawlStatus::Start; + + if let Some(u) = &domain_parsed { + if check_firewall && crate::utils::abs::block_website(u) { + status = CrawlStatus::FirewallBlocked; + } + } + + Self { + configuration: Configuration::new().into(), + status, + domain_parsed, + url, + #[cfg(feature = "disk")] + enable_sqlite: true, + ..Default::default() + } + } + + /// Initialize the Website with a starting link to crawl. + pub fn new(url: &str) -> Self { + Website::_new(url, true) + } + + /// Initialize the Website with a starting link to crawl and check the firewall. + pub fn new_with_firewall(url: &str, check_firewall: bool) -> Self { + Website::_new(url, check_firewall) + } + + /// Setup a shared database. + #[cfg(feature = "disk")] + pub fn setup_database_handler(&self) -> Box { + Box::new(DatabaseHandler::new(&Some(self.target_id()))) + } + + #[cfg(feature = "disk")] + /// Setup the sqlist usage. + pub fn setup_shared_db(&mut self, db: Box) { + self.sqlite = Some(db) + } + + #[cfg(feature = "disk")] + /// Setup the sqlist usage. + pub fn setup_sqlite(&mut self) { + if self.sqlite.is_none() { + self.sqlite = Some(self.setup_database_handler()) + } + } + + /// Set the url of the website to re-use configuration and data. + pub fn set_url(&mut self, url: &str) -> &mut Self { + let url = if url.starts_with(' ') || url.ends_with(' ') { + url.trim() + } else { + url + }; + + let domain: Box = if networking_capable(url) { + CaseInsensitiveString::new(&url).into() + } else { + CaseInsensitiveString::new(&prepare_url(url)).into() + }; + + self.domain_parsed = parse_absolute_url(&domain); + self.url = domain; + self + } + + /// Set the direct url of the website to re-use configuration and data without parsing the domain. + pub fn set_url_only(&mut self, url: &str) -> &mut Self { + self.url = CaseInsensitiveString::new(&url).into(); + self + } + + /// Get the target id for a crawl. This takes the crawl ID and the url and concats it without delimiters. + pub fn target_id(&self) -> String { + string_concat!(self.crawl_id, self.url.inner()) + } + + /// Single page request. + pub fn single_page(&self) -> bool { + match &self.configuration.inner_budget { + Some(b) => match b.get(&*WILD_CARD_PATH) { + Some(b) => b.eq(&1), + _ => false, + }, + _ => false, + } + } + + /// Setup SQLite. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub fn setup_disk(&mut self) { + if self.enable_sqlite && self.sqlite.is_none() { + self.setup_sqlite(); + } + // run full on sqlite. + if self.configuration.shared { + if let Some(sqlite) = self.sqlite.as_mut() { + sqlite.seeded = true; + // sqlite.persist = true; + } + } + } + + #[cfg(feature = "disk")] + /// Set the sqlite disk persistance. + pub fn set_disk_persistance(&mut self, persist: bool) -> &mut Self { + if self.enable_sqlite && self.sqlite.is_some() { + if let Some(sqlite) = self.sqlite.as_mut() { + sqlite.persist = persist; + } + } + self + } + + /// Setup SQLite. This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub fn setup_disk(&mut self) {} + + /// Get the robots.txt parser. + pub fn get_robots_parser(&self) -> &Option> { + &self.robot_file_parser + } + + /// Does the website require javascript to run? + pub fn get_requires_javascript(&self) -> bool { + self.website_meta_info == WebsiteMetaInfo::RequiresJavascript + } + + /// Get the website meta information that can help with retry handling. + pub fn get_website_meta_info(&self) -> &WebsiteMetaInfo { + &self.website_meta_info + } + + /// Check if URL exists (ignore case). This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn is_allowed_disk(&self, url_to_check: &str) -> bool { + match &self.sqlite { + Some(sqlite) => { + if !sqlite.ready() { + true + } else { + let db_pool = sqlite.get_db_pool().await; + let allowed = sqlite.url_exists(db_pool, url_to_check).await; + + !allowed + } + } + _ => true, + } + } + + /// Check if URL exists (ignore case). This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn is_allowed_disk(&self, _url_to_check: &str) -> bool { + true + } + + /// Check if signature exists (ignore case). This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn is_allowed_signature_disk(&self, signature_to_check: u64) -> bool { + match &self.sqlite { + Some(sqlite) => { + if !sqlite.ready() { + true + } else { + let db_pool = sqlite.get_db_pool().await; + + !sqlite.signature_exists(db_pool, signature_to_check).await + } + } + _ => true, + } + } + + /// Check if signature exists (ignore case). This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn is_allowed_signature_disk(&self, _signature_to_check: u64) -> bool { + true + } + + /// Is the signature allowed. + pub async fn is_signature_allowed(&self, signature: u64) -> bool { + !self.signatures.contains(&signature) || self.is_allowed_signature_disk(signature).await + } + + /// Clear the disk. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn clear_disk(&self) { + if let Some(sqlite) = &self.sqlite { + if sqlite.pool_inited() { + let _ = DatabaseHandler::clear_table(sqlite.get_db_pool().await).await; + } + } + } + + /// Clear the disk. This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn clear_disk(&self) {} + + /// Check if the disk is enabled. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub(crate) fn shared_disk_enabled(&self) -> bool { + self.configuration.shared && self.sqlite.is_some() + } + + /// Insert a new URL to disk if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn insert_url_disk(&self, new_url: &str) { + if let Some(sqlite) = &self.sqlite { + sqlite.insert_url(sqlite.get_db_pool().await, new_url).await + } + } + + /// Insert a new signature to disk if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn insert_signature_disk(&self, signature: u64) { + if let Some(sqlite) = &self.sqlite { + sqlite + .insert_signature(sqlite.get_db_pool().await, signature) + .await + } + } + + /// Insert a new URL if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn insert_link(&mut self, new_url: CaseInsensitiveString) { + let mem_load = crate::utils::detect_system::get_global_memory_state().await; + let beyond_memory_limits = self.links_visited.len() >= *LINKS_VISITED_MEMORY_LIMIT; + let seed_check = mem_load == 2 || mem_load == 1 || beyond_memory_limits; + + if seed_check { + let mut seeded = false; + if let Some(sqlite) = &self.sqlite { + if !sqlite.ready() { + let _ = self.seed().await; + seeded = true; + } + } + if let Some(sqlite) = self.sqlite.as_mut() { + sqlite.set_seeded(seeded); + } + } + + if mem_load == 2 || beyond_memory_limits || self.shared_disk_enabled() { + self.insert_url_disk(&new_url).await + } else if mem_load == 1 { + if self.links_visited.len() <= 100 { + self.links_visited.insert(new_url); + } else { + self.insert_url_disk(&new_url).await + } + } else { + self.links_visited.insert(new_url); + } + } + + /// Insert a new URL if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn insert_link(&mut self, link: CaseInsensitiveString) { + self.links_visited.insert(link); + } + + /// Insert a new signature if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn insert_signature(&mut self, new_signature: u64) { + let mem_load = crate::utils::detect_system::get_global_memory_state().await; + let beyond_memory_limits = self.signatures.len() >= *LINKS_VISITED_MEMORY_LIMIT; + let seed_check = mem_load == 2 || mem_load == 1 || beyond_memory_limits; + + if seed_check { + let mut seeded = false; + if let Some(sqlite) = &self.sqlite { + if !sqlite.ready() { + let _ = self.seed().await; + seeded = true; + } + } + if let Some(sqlite) = self.sqlite.as_mut() { + sqlite.set_seeded(seeded); + } + } + + if mem_load == 2 || beyond_memory_limits || self.shared_disk_enabled() { + self.insert_signature_disk(new_signature).await + } else if mem_load == 1 { + if self.signatures.len() <= 100 { + self.signatures.insert(new_signature); + } else { + self.insert_signature_disk(new_signature).await + } + } else { + self.signatures.insert(new_signature); + } + } + + /// Insert a new signature if it doesn't exist. This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn insert_signature(&mut self, new_signature: u64) { + self.signatures.insert(new_signature); + } + + /// Seed the DB and clear the Hashset. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn seed(&mut self) -> Result<(), sqlx::Error> { + let links = self.get_links(); + + if let Some(sqlite) = &self.sqlite { + if let Ok(links) = sqlite.seed(sqlite.get_db_pool().await, links).await { + self.links_visited.clear(); + + for link in links { + self.links_visited.insert(link); + } + + if let Some(sqlite) = self.sqlite.as_mut() { + sqlite.seeded = true; + } + } + } + + Ok(()) + } + + /// Return `false` if the crawl should shutdown. Process in between each link. + async fn handle_process( + &self, + handle: &Option>, + interval: &mut Interval, + shutdown: T, + ) -> bool + where + T: std::future::Future, + { + if self.shutdown { + (shutdown).await; + false + } else { + match handle.as_ref() { + Some(handle) => { + while handle.load(Ordering::Relaxed) == 1 { + interval.tick().await; + } + if handle.load(Ordering::Relaxed) == 2 { + (shutdown).await; + false + } else { + true + } + } + _ => true, + } + } + } + + /// return `true` if URL: + /// + /// - is not already crawled + /// - is not over depth + /// - is not over crawl budget + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(not(feature = "regex"))] + pub fn is_allowed(&mut self, link: &CaseInsensitiveString) -> ProcessLinkStatus { + let status = self.is_allowed_budgetless(link); + + if status.eq(&ProcessLinkStatus::Allowed) && self.is_over_budget(link) { + return ProcessLinkStatus::BudgetExceeded; + } + + status + } + + /// return `true` if URL: + /// + /// - is not already crawled + /// - is not over depth + /// - is not over crawl budget + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(feature = "regex")] + pub fn is_allowed(&mut self, link: &CaseInsensitiveString) -> ProcessLinkStatus { + let status = self.is_allowed_budgetless(link); + + if status.eq(&ProcessLinkStatus::Allowed) && self.is_over_budget(link) { + return ProcessLinkStatus::BudgetExceeded; + } + status + } + + /// return `true` if URL: + /// + /// - is not already crawled + /// - is not over depth + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(not(feature = "regex"))] + pub fn is_allowed_budgetless(&mut self, link: &CaseInsensitiveString) -> ProcessLinkStatus { + if self.links_visited.contains(link) { + ProcessLinkStatus::Blocked + } else { + let status = self.is_allowed_default(link.inner()); + + if status.eq(&ProcessLinkStatus::Allowed) && self.is_over_depth(link) { + return ProcessLinkStatus::Blocked; + } + + status + } + } + + /// return `true` if URL: + /// + /// - is not already crawled + /// - is not over depth + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(feature = "regex")] + pub fn is_allowed_budgetless(&mut self, link: &CaseInsensitiveString) -> ProcessLinkStatus { + if self.links_visited.contains(link) { + ProcessLinkStatus::Blocked + } else { + let status = self.is_allowed_default(link); + if status.eq(&ProcessLinkStatus::Allowed) && self.is_over_depth(link) { + return ProcessLinkStatus::Blocked; + } + status + } + } + + /// return `true` if URL: + /// + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(feature = "regex")] + pub fn is_allowed_default(&self, link: &CaseInsensitiveString) -> ProcessLinkStatus { + let blacklist = self.configuration.get_blacklist_compiled(); + let whitelist = self.configuration.get_whitelist_compiled(); + + let blocked_whitelist = !whitelist.is_empty() && !contains(whitelist, link.inner()); + let blocked_blacklist = !blacklist.is_empty() && contains(blacklist, link.inner()); + + if blocked_whitelist || blocked_blacklist || !self.is_allowed_robots(link.as_ref()) { + ProcessLinkStatus::Blocked + } else { + ProcessLinkStatus::Allowed + } + } + + /// return `true` if URL: + /// + /// - is optionally whitelisted + /// - is not blacklisted + /// - is not forbidden in robot.txt file (if parameter is defined) + #[inline] + #[cfg(not(feature = "regex"))] + pub fn is_allowed_default(&self, link: &CompactString) -> ProcessLinkStatus { + let whitelist = self.configuration.get_whitelist_compiled(); + let blacklist = self.configuration.get_blacklist_compiled(); + + let blocked_whitelist = !whitelist.is_empty() && !contains(whitelist, link); + let blocked_blacklist = !blacklist.is_empty() && contains(blacklist, link); + + if blocked_whitelist || blocked_blacklist || !self.is_allowed_robots(link) { + ProcessLinkStatus::Blocked + } else { + ProcessLinkStatus::Allowed + } + } + + /// return `true` if URL: + /// + /// - is not forbidden in robot.txt file (if parameter is defined) + pub fn is_allowed_robots(&self, link: &str) -> bool { + if self.configuration.respect_robots_txt { + if let Some(r) = &self.robot_file_parser { + return r.can_fetch( + match &self.configuration.user_agent { + Some(ua) => ua, + _ => "*", + }, + link, + ); + } + } + + true + } + + /// Detect if the inner budget is exceeded + pub(crate) fn is_over_inner_depth_budget(&mut self, link: &CaseInsensitiveString) -> bool { + let mut over = false; + + if let Some(segments) = get_path_from_url(link) + .strip_prefix('/') + .map(|remainder| remainder.split('/')) + { + let mut depth: usize = 0; + + for _ in segments { + depth = depth.saturating_add(1); + if depth > self.configuration.depth_distance { + over = true; + break; + } + } + } + + over + } + + /// is over the wild card budget. + #[cfg(feature = "sitemap")] + pub(crate) fn is_over_wild_budget( + &self, + budget: &Option>, + ) -> bool { + let exceeded_wild_budget = if self.configuration.wild_card_budgeting { + match budget { + Some(budget) => match budget.get(&*WILD_CARD_PATH) { + Some(budget) => budget.abs_diff(0) == 1, + _ => false, + }, + _ => false, + } + } else { + false + }; + exceeded_wild_budget + } + + /// Detect if the inner budget is exceeded + pub(crate) fn is_over_inner_budget(&mut self, link: &CaseInsensitiveString) -> bool { + match self.configuration.inner_budget.as_mut() { + Some(budget) => { + let exceeded_wild_budget = if self.configuration.wild_card_budgeting { + match budget.get_mut(&*WILD_CARD_PATH) { + Some(budget) => { + if budget.abs_diff(0) == 1 { + true + } else { + *budget -= 1; + false + } + } + _ => false, + } + } else { + false + }; + + // set this up prior to crawl to avoid checks per link. + // If only the wild card budget is set we can safely skip all checks. + let skip_paths = self.configuration.wild_card_budgeting && budget.len() == 1; + let has_depth_control = self.configuration.depth_distance > 0; + + // check if paths pass + if !skip_paths && !exceeded_wild_budget { + let path_segments = get_path_from_url(link) + .strip_prefix('/') + .map(|remainder| remainder.split('/')); + + match path_segments { + Some(segments) => { + let mut joint_segment = CaseInsensitiveString::default(); + let mut over = false; + let mut depth: usize = 0; + + for seg in segments { + if has_depth_control { + depth = depth.saturating_add(1); + if depth > self.configuration.depth_distance { + over = true; + break; + } + } + + joint_segment.push_str(seg); + + if budget.contains_key(&joint_segment) { + if let Some(budget) = budget.get_mut(&joint_segment) { + if budget.abs_diff(0) == 0 || *budget == 0 { + over = true; + break; + } else { + *budget -= 1; + continue; + } + } + } + } + + over + } + _ => false, + } + } else { + exceeded_wild_budget + } + } + _ => false, + } + } + + /// Validate if url exceeds crawl depth and should be ignored. + pub(crate) fn is_over_depth(&mut self, link: &CaseInsensitiveString) -> bool { + self.configuration.depth_distance > 0 && self.is_over_inner_depth_budget(link) + } + + /// Validate if url exceeds crawl budget and should not be handled. + pub(crate) fn is_over_budget(&mut self, link: &CaseInsensitiveString) -> bool { + self.is_over_inner_budget(link) + } + + /// Restore one wildcard budget credit (for relevance-gated irrelevant pages). + #[cfg(all(feature = "agent", feature = "serde"))] + pub(crate) fn restore_wildcard_budget(&mut self) { + if self.configuration.wild_card_budgeting { + if let Some(budget) = self.configuration.inner_budget.as_mut() { + if let Some(counter) = budget.get_mut(&*WILD_CARD_PATH) { + *counter = counter.saturating_add(1); + } + } + } + } + + /// Amount of pages crawled in memory only. Use get_size for full links between memory and disk. + pub fn size(&self) -> usize { + self.links_visited.len() + } + + /// Get the amount of resources collected. + #[cfg(not(feature = "disk"))] + pub async fn get_size(&self) -> usize { + self.links_visited.len() + } + + /// Get the amount of resources collected. + #[cfg(feature = "disk")] + pub async fn get_size(&self) -> usize { + let disk_count = if let Some(sqlite) = &self.sqlite { + if sqlite.pool_inited() { + let disk_count = DatabaseHandler::count_records(sqlite.get_db_pool().await).await; + + disk_count.unwrap_or_default() as usize + } else { + 0 + } + } else { + 0 + }; + + let mut mem_count = self.links_visited.len(); + + if mem_count >= *LINKS_VISITED_MEMORY_LIMIT { + mem_count -= *LINKS_VISITED_MEMORY_LIMIT; + } + + disk_count + mem_count + } + + /// Drain the extra links used for things like the sitemap. + pub fn drain_extra_links(&mut self) -> hashbrown::hash_set::Drain<'_, CaseInsensitiveString> { + self.extra_links.drain() + } + + /// Set the initial status code of the request. + pub fn set_initial_status_code(&mut self, initial_status_code: StatusCode) { + self.initial_status_code = initial_status_code; + } + + /// Get the initial status code of the request. + pub fn get_initial_status_code(&self) -> &StatusCode { + &self.initial_status_code + } + + /// Set the initial html size of the request. + pub fn set_initial_html_length(&mut self, initial_html_length: usize) { + self.initial_html_length = initial_html_length; + } + + /// Get the initial html size of the request. + pub fn get_initial_html_length(&self) -> usize { + self.initial_html_length + } + + /// Set the initial anti-bot tech code used for the intitial request. + pub fn set_initial_anti_bot_tech(&mut self, initial_anti_bot_tech: AntiBotTech) { + self.initial_anti_bot_tech = initial_anti_bot_tech; + } + + /// Get the initial anti-bot tech code used for the intitial request. + pub fn get_initial_anti_bot_tech(&self) -> &AntiBotTech { + &self.initial_anti_bot_tech + } + + /// Set the initial waf detected used for the intitial request + pub fn set_initial_page_waf_check(&mut self, initial_page_waf_check: bool) { + self.initial_page_waf_check = initial_page_waf_check; + } + + /// Get the initial waf detected used for the intitial request. + pub fn get_initial_page_waf_check(&self) -> bool { + self.initial_page_waf_check + } + + /// Set the initial page should retry determination used for the intitial request. + pub fn set_initial_page_should_retry(&mut self, initial_page_should_retry: bool) { + self.initial_page_should_retry = initial_page_should_retry; + } + + /// Get the initial page should retry determination used for the intitial request. + pub fn get_initial_page_should_retry(&self) -> bool { + self.initial_page_should_retry + } + + /// Drain the links visited. + #[cfg(any( + feature = "string_interner_bucket_backend", + feature = "string_interner_string_backend", + feature = "string_interner_buffer_backend", + ))] + pub fn drain_links( + &mut self, + ) -> hashbrown::hash_set::Drain<'_, string_interner::symbol::SymbolUsize> { + self.links_visited.drain() + } + + #[cfg(not(any( + feature = "string_interner_bucket_backend", + feature = "string_interner_string_backend", + feature = "string_interner_buffer_backend", + )))] + /// Drain the links visited. + pub fn drain_links(&mut self) -> hashbrown::hash_set::Drain<'_, CaseInsensitiveString> { + self.links_visited.drain() + } + + /// Drain the signatures visited. + #[cfg(any( + feature = "string_interner_bucket_backend", + feature = "string_interner_string_backend", + feature = "string_interner_buffer_backend", + ))] + pub fn drain_signatures(&mut self) -> hashbrown::hash_set::Drain<'_, u64> { + self.signatures.drain() + } + + #[cfg(not(any( + feature = "string_interner_bucket_backend", + feature = "string_interner_string_backend", + feature = "string_interner_buffer_backend", + )))] + /// Drain the signatures visited. + pub fn drain_signatures(&mut self) -> hashbrown::hash_set::Drain<'_, u64> { + self.signatures.drain() + } + + /// Set extra links to crawl. This could be used in conjuntion with 'website.persist_links' to extend the crawl on the next run. + pub fn set_extra_links( + &mut self, + extra_links: HashSet, + ) -> &HashSet { + self.extra_links.extend(extra_links); + &self.extra_links + } + + /// Get the extra links. + pub fn get_extra_links(&self) -> &HashSet { + &self.extra_links + } + + /// Clear all pages, disk, and links stored in memory. + pub async fn clear_all(&mut self) { + self.clear(); + self.clear_disk().await; + } + + /// Clear all pages and links stored in memory. + pub fn clear(&mut self) { + self.links_visited.clear(); + self.signatures.clear(); + self.pages.take(); + self.extra_links.clear(); + } + + /// Get the HTTP request client. The client is set after the crawl has started. + pub fn get_client(&self) -> &Option { + &self.client + } + + /// Page getter. + pub fn get_pages(&self) -> Option<&Vec> { + self.pages.as_ref() + } + + /// Links visited getter for disk. This does nothing with `disk` flag enabled. + #[cfg(not(feature = "disk"))] + pub async fn get_links_disk(&self) -> HashSet { + Default::default() + } + + /// Links visited getter for disk. This does nothing with `disk` flag enabled. + #[cfg(feature = "disk")] + pub async fn get_links_disk(&self) -> HashSet { + if let Some(sqlite) = &self.sqlite { + if sqlite.pool_inited() { + if let Ok(links) = + DatabaseHandler::get_all_resources(sqlite.get_db_pool().await).await + { + links + } else { + Default::default() + } + } else { + Default::default() + } + } else { + Default::default() + } + } + + /// Links all the links visited between memory and disk. + #[cfg(feature = "disk")] + pub async fn get_all_links_visited(&self) -> HashSet { + let mut l = self.get_links_disk().await; + let m = self.links_visited.get_links(); + + l.extend(m); + + l + } + + /// Links all the links visited between memory and disk. + #[cfg(not(feature = "disk"))] + pub async fn get_all_links_visited(&self) -> HashSet { + self.get_links() + } + + /// Links visited getter for memory resources. + pub fn get_links(&self) -> HashSet { + self.links_visited.get_links() + } + + /// Domain parsed url getter. + pub fn get_url_parsed(&self) -> &Option> { + &self.domain_parsed + } + + /// Domain name getter. + pub fn get_url(&self) -> &CaseInsensitiveString { + &self.url + } + + /// Crawl delay getter. + pub fn get_delay(&self) -> Duration { + Duration::from_millis(self.configuration.delay) + } + + /// Get the active crawl status. + pub fn get_status(&self) -> &CrawlStatus { + &self.status + } + + /// Set the active crawl status. This is helpful when chaining crawls concurrently. + pub fn set_status(&mut self, status: CrawlStatus) -> &CrawlStatus { + self.status = status; + &self.status + } + + /// Reset the active crawl status to bypass websites that are blocked. + pub fn reset_status(&mut self) -> &CrawlStatus { + self.status = CrawlStatus::Start; + &self.status + } + + /// Set the crawl status to persist between the run. + /// Example crawling a sitemap and all links after - website.crawl_sitemap().await.persist_links().crawl().await + pub fn persist_links(&mut self) -> &mut Self { + self.status = CrawlStatus::Active; + self + } + + /// Absolute base url of crawl. + pub fn get_absolute_path(&self, domain: Option<&str>) -> Option { + if domain.is_some() { + url::Url::parse(domain.unwrap_or_default()) + .ok() + .map(|mut url| { + if let Ok(mut path) = url.path_segments_mut() { + path.clear(); + } + url + }) + } else if let Some(mut d) = self.domain_parsed.as_deref().cloned() { + if let Ok(mut path) = d.path_segments_mut() { + path.clear(); + } + Some(d) + } else { + None + } + } + + /// Stop all crawls for the website. + pub fn stop(&mut self) { + self.shutdown = true; + } + + /// Crawls commenced from fresh run. + pub fn start(&mut self) { + self.shutdown = false; + } + + /// configure the robots parser on initial crawl attempt and run. + pub async fn configure_robots_parser(&mut self, client: &Client) { + if self.configuration.respect_robots_txt { + let robot_file_parser = self + .robot_file_parser + .get_or_insert_with(RobotFileParser::new); + + if robot_file_parser.mtime() <= 4000 { + let host_str = match &self.domain_parsed { + Some(domain) => domain.as_str(), + _ => self.url.inner(), + }; + + if !host_str.is_empty() { + if host_str.ends_with('/') { + robot_file_parser.read(client, host_str).await; + } else { + robot_file_parser + .read(client, &string_concat!(host_str, "/")) + .await; + } + } + if let Some(delay) = + robot_file_parser.get_crawl_delay(&self.configuration.user_agent) + { + self.configuration.delay = delay.as_millis().min(60000) as u64; + } + } + } + } + + /// Setup strict a strict redirect policy for request. All redirects need to match the host. + pub fn setup_strict_policy(&self) -> Policy { + use crate::client::redirect::Attempt; + use crate::page::domain_name; + use std::sync::atomic::AtomicU8; + + let default_policy = Policy::default(); + + match self.domain_parsed.as_deref().cloned() { + Some(host_s) => { + let initial_redirect_limit = if self.configuration.respect_robots_txt { + 2 + } else { + 1 + }; + let subdomains = self.configuration.subdomains; + let tld = self.configuration.tld; + let host_domain_name = if tld { + domain_name(&host_s).to_string() + } else { + Default::default() + }; + let redirect_limit = self.configuration.redirect_limit; + + let custom_policy = { + let initial_redirect = Arc::new(AtomicU8::new(0)); + + move |attempt: Attempt| { + if tld && domain_name(attempt.url()) == host_domain_name + || subdomains + && attempt + .url() + .host_str() + .unwrap_or_default() + .ends_with(host_s.host_str().unwrap_or_default()) + || attempt.url().host() == host_s.host() + { + default_policy.redirect(attempt) + } else if attempt.previous().len() > redirect_limit { + attempt.error("too many redirects") + } else if attempt.status().is_redirection() + && (0..initial_redirect_limit) + .contains(&initial_redirect.load(Ordering::Relaxed)) + { + initial_redirect.fetch_add(1, Ordering::Relaxed); + default_policy.redirect(attempt) + } else { + attempt.stop() + } + } + }; + Policy::custom(custom_policy) + } + _ => default_policy, + } + } + + /// Setup redirect policy for reqwest. + pub fn setup_redirect_policy(&self) -> Policy { + match self.configuration.redirect_policy { + RedirectPolicy::Loose => Policy::limited(self.configuration.redirect_limit), + RedirectPolicy::None => Policy::none(), + RedirectPolicy::Strict => self.setup_strict_policy(), + } + } + + /// Configure the headers to use. + pub fn configure_headers(&mut self) { + let mut headers: reqwest::header::HeaderMap = reqwest::header::HeaderMap::new(); + + let user_agent = match &self.configuration.user_agent { + Some(ua) => ua.as_str(), + _ => get_ua(self.configuration.only_chrome_agent()), + }; + + if self.configuration.modify_headers { + crate::utils::header_utils::extend_headers( + &mut headers, + user_agent, + &self.configuration.headers, + &None, + &self.configuration.viewport, + &self.domain_parsed, + ); + + if !headers.is_empty() { + // always remove the referer header. + if let Some(referer) = headers.remove(REFERER) { + if let Ok(v) = referer.to_str() { + // modify the default referer + if self.configuration.referer.is_none() && !v.is_empty() { + self.configuration.referer = Some(v.into()) + } + } + } + self.configuration + .headers + .replace(Box::new(SerializableHeaderMap::from(headers))); + } + } + } + + #[cfg(all( + any(not(feature = "wreq"), feature = "cache_request"), + not(feature = "decentralized") + ))] + /// Base client configuration. + pub fn configure_base_client(&self) -> ClientBuilder { + let policy = self.setup_redirect_policy(); + + let user_agent = match &self.configuration.user_agent { + Some(ua) => ua.as_str(), + _ => get_ua(self.configuration.only_chrome_agent()), + }; + + // let missing_host = + // !headers.contains_key(crate::client::header::HOST) && !headers.contains_key("Host"); + let missing_agent = match &self.configuration.headers { + Some(headers) => { + !headers.contains_key(crate::client::header::USER_AGENT) + && !headers.contains_key("User-Agent") + } + _ => true, + }; + + let timeout_mult = if self.configuration.proxies.is_some() { + 2 + } else { + 1 + }; + + let client = reqwest::Client::builder() + .redirect(policy) + .http09_responses() + .http1_ignore_invalid_headers_in_responses(true) + .referer(self.configuration.referer.is_none()) + .connect_timeout( + self.configuration + .default_http_connect_timeout + .unwrap_or(Duration::from_secs(24 * timeout_mult)), + ) + .read_timeout( + self.configuration + .default_http_read_timeout + .unwrap_or(Duration::from_secs(42 * timeout_mult)), + ) + .http1_title_case_headers() + .http1_allow_obsolete_multiline_headers_in_responses(true) + .http1_allow_spaces_after_header_name_in_responses(true) + // .http1_preserve_header_order() + // .http1_preserve_header_case() + .danger_accept_invalid_certs(self.configuration.accept_invalid_certs); + + let client = if let Some(network_interface) = &self.configuration.network_interface { + set_interface(client, network_interface) + } else { + client + }; + + let client = if let Some(local_address) = &self.configuration.local_address { + client.local_address(*local_address) + } else { + client + }; + + let client = if self.configuration.proxies.is_none() { + client + } else { + client.tcp_keepalive(Duration::from_secs(30)) + }; + + // check both casing for user-agent + let client = if missing_agent { + client.user_agent(user_agent) + } else { + client + }; + + let client = if self.configuration.http2_prior_knowledge { + client.http2_prior_knowledge() + } else { + client + }; + + crate::utils::header_utils::setup_default_headers(client, &self.configuration) + } + + #[cfg(all( + feature = "wreq", + not(feature = "decentralized"), + not(feature = "cache_request") + ))] + /// Base client configuration. + pub fn configure_base_client(&self) -> ClientBuilder { + let policy = self.setup_redirect_policy(); + + let user_agent = match &self.configuration.user_agent { + Some(ua) => ua.as_str(), + _ => get_ua(self.configuration.only_chrome_agent()), + }; + + let missing_agent = match &self.configuration.headers { + Some(headers) => { + !headers.contains_key(crate::client::header::USER_AGENT) + && !headers.contains_key("User-Agent") + } + _ => true, + }; + + let timeout_mult = if self.configuration.proxies.is_some() { + 2 + } else { + 1 + }; + + let client = Client::builder() + .redirect(policy) + .referer(self.configuration.referer.is_none()) + .connect_timeout( + self.configuration + .default_http_connect_timeout + .unwrap_or(Duration::from_secs(24 * timeout_mult)), + ) + .read_timeout( + self.configuration + .default_http_read_timeout + .unwrap_or(Duration::from_secs(42 * timeout_mult)), + ); + + let client = if let Some(local_address) = &self.configuration.local_address { + client.local_address(*local_address) + } else { + client + }; + + let client = if self.configuration.proxies.is_none() { + client + } else { + client.tcp_keepalive(Duration::from_secs(30)) + }; + + let client = if missing_agent { + client.user_agent(user_agent) + } else { + client + }; + + let client = if let Some(emulation) = self.configuration.emulation { + client.emulation(emulation) + } else { + client + }; + + crate::utils::header_utils::setup_default_headers(client, &self.configuration) + } + + /// Build the HTTP client. + #[cfg(all(not(feature = "decentralized"), not(feature = "cache_request")))] + pub fn configure_http_client_builder(&self) -> ClientBuilder { + let client = self.configure_base_client(); + + let mut client = match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + }; + + let client = match &self.configuration.proxies { + Some(proxies) => { + let linux = cfg!(target_os = "linux"); + let ignore_plain_socks = proxies.len() >= 2 && linux; + let replace_plain_socks = proxies.len() == 1 && linux; + + for proxie in proxies.iter() { + if proxie.ignore == crate::configuration::ProxyIgnore::Http { + continue; + } + + let proxie = &proxie.addr; + let socks = proxie.starts_with("socks://"); + + // we can skip it and use another proxy from the list. + if ignore_plain_socks && socks { + continue; + } + + // use HTTP instead as reqwest does not support the protocol on linux. + if replace_plain_socks && socks { + if let Ok(proxy) = + crate::client::Proxy::all(proxie.replacen("socks://", "http://", 1)) + { + client = client.proxy(proxy); + } + } else if let Ok(proxy) = crate::client::Proxy::all(proxie) { + client = client.proxy(proxy); + } + } + + client + } + _ => client, + }; + + // Spider Cloud proxy injection (modes that use proxy transport) + #[cfg(feature = "spider_cloud")] + let client = if let Some(ref sc) = self.configuration.spider_cloud { + if sc.uses_proxy() { + match ( + crate::client::Proxy::all(&sc.proxy_url), + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", sc.api_key)), + ) { + (Ok(proxy), Ok(auth_value)) => client.proxy(proxy.custom_http_auth(auth_value)), + _ => client, + } + } else { + client + } + } else { + client + }; + + let client = if crate::utils::connect::background_connect_threading() { + client.connector_layer(crate::utils::connect::BackgroundProcessorLayer::new()) + } else { + client + }; + + let client = match self.configuration.concurrency_limit { + Some(limit) => { + client.connector_layer(tower::limit::concurrency::ConcurrencyLimitLayer::new(limit)) + } + _ => client, + }; + + self.configure_http_client_cookies(client) + } + + /// Build the HTTP client with caching enabled. + #[cfg(all(not(feature = "decentralized"), feature = "cache_request"))] + pub fn configure_http_client_builder(&self) -> reqwest_middleware::ClientBuilder { + use crate::utils::create_cache_key; + let client = self.configure_base_client(); + + let mut client = match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + }; + + let client = match &self.configuration.proxies { + Some(proxies) => { + let linux = cfg!(target_os = "linux"); + let ignore_plain_socks = proxies.len() >= 2 && linux; + let replace_plain_socks = proxies.len() == 1 && linux; + + for proxie in proxies.iter() { + if proxie.ignore == crate::configuration::ProxyIgnore::Http { + continue; + } + let proxie = &proxie.addr; + + let socks = proxie.starts_with("socks://"); + + // we can skip it and use another proxy from the list. + if ignore_plain_socks && socks { + continue; + } + + // use HTTP instead as reqwest does not support the protocol on linux. + if replace_plain_socks && socks { + if let Ok(proxy) = + crate::client::Proxy::all(proxie.replacen("socks://", "http://", 1)) + { + client = client.proxy(proxy); + } + } else if let Ok(proxy) = crate::client::Proxy::all(proxie) { + client = client.proxy(proxy); + } + } + + client + } + _ => client, + }; + + // Spider Cloud proxy injection (modes that use proxy transport) + #[cfg(feature = "spider_cloud")] + let client = if let Some(ref sc) = self.configuration.spider_cloud { + if sc.uses_proxy() { + match ( + crate::client::Proxy::all(&sc.proxy_url), + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", sc.api_key)), + ) { + (Ok(proxy), Ok(auth_value)) => client.proxy(proxy.custom_http_auth(auth_value)), + _ => client, + } + } else { + client + } + } else { + client + }; + + let client = self.configure_http_client_cookies(client); + + let client = if crate::utils::connect::background_connect_threading() { + client.connector_layer(crate::utils::connect::BackgroundProcessorLayer::new()) + } else { + client + }; + + let client = match self.configuration.concurrency_limit { + Some(limit) => { + client.connector_layer(tower::limit::concurrency::ConcurrencyLimitLayer::new(limit)) + } + _ => client, + }; + + let client = + reqwest_middleware::ClientBuilder::new(unsafe { client.build().unwrap_unchecked() }); + + if self.configuration.cache { + let mut cache_options = HttpCacheOptions::default(); + + cache_options.cache_key = Some(Arc::new(|req: &http::request::Parts| { + let mut auth_token = None; + if let Some(auth) = req.headers.get("authorization") { + if let Ok(token) = auth.to_str() { + if !token.is_empty() { + auth_token = Some(token); + } + } + } + create_cache_key(req, Some(req.method.as_str()), auth_token) + })); + client.with(Cache(HttpCache { + mode: CacheMode::Default, + manager: CACACHE_MANAGER.clone(), + options: cache_options, + })) + } else { + client + } + } + + /// Build the HTTP client with cookie configurations. + #[cfg(all(not(feature = "decentralized"), feature = "cookies"))] + pub fn configure_http_client_cookies( + &self, + client: crate::client::ClientBuilder, + ) -> crate::client::ClientBuilder { + let client = client.cookie_provider(self.cookie_jar.clone()); + + if !self.configuration.cookie_str.is_empty() { + if let Some(url) = self.domain_parsed.as_ref() { + self.cookie_jar + .add_cookie_str(&self.configuration.cookie_str, url); + } + } + + client + } + + /// Build the client with cookie configurations. This does nothing with [cookies] flag enabled. + #[cfg(all(not(feature = "decentralized"), not(feature = "cookies")))] + pub fn configure_http_client_cookies( + &self, + client: crate::client::ClientBuilder, + ) -> crate::client::ClientBuilder { + client + } + + /// Set the HTTP client to use directly. This is helpful if you manually call 'website.configure_http_client' before the crawl. + pub fn set_http_client(&mut self, client: Client) -> &Option { + self.client = Some(client); + &self.client + } + + /// Build a client configured with a single proxy for use in rotation. + #[cfg(all(not(feature = "decentralized"), not(feature = "cache_request")))] + fn build_single_proxy_client( + &self, + proxy: &crate::configuration::RequestProxy, + ) -> Option { + if proxy.ignore == crate::configuration::ProxyIgnore::Http { + return None; + } + + let client = self.configure_base_client(); + + let client = match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + }; + + let addr = &proxy.addr; + let linux = cfg!(target_os = "linux"); + let socks = addr.starts_with("socks://"); + + let client = if socks && linux { + match crate::client::Proxy::all(addr.replacen("socks://", "http://", 1)) { + Ok(p) => client.proxy(p), + Err(_) => return None, + } + } else { + match crate::client::Proxy::all(addr) { + Ok(p) => client.proxy(p), + Err(_) => return None, + } + }; + + #[cfg(feature = "spider_cloud")] + let client = if let Some(ref sc) = self.configuration.spider_cloud { + if sc.uses_proxy() { + match ( + crate::client::Proxy::all(&sc.proxy_url), + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", sc.api_key)), + ) { + (Ok(proxy), Ok(auth_value)) => client.proxy(proxy.custom_http_auth(auth_value)), + _ => client, + } + } else { + client + } + } else { + client + }; + + let client = if crate::utils::connect::background_connect_threading() { + client.connector_layer(crate::utils::connect::BackgroundProcessorLayer::new()) + } else { + client + }; + + let client = match self.configuration.concurrency_limit { + Some(limit) => { + client.connector_layer(tower::limit::concurrency::ConcurrencyLimitLayer::new(limit)) + } + _ => client, + }; + + let client = self.configure_http_client_cookies(client); + unsafe { Some(client.build().unwrap_unchecked()) } + } + + /// Build a client configured with a single proxy for use in rotation (cache_request variant). + #[cfg(all(not(feature = "decentralized"), feature = "cache_request"))] + fn build_single_proxy_client( + &self, + proxy: &crate::configuration::RequestProxy, + ) -> Option { + use crate::utils::create_cache_key; + + if proxy.ignore == crate::configuration::ProxyIgnore::Http { + return None; + } + + let client = self.configure_base_client(); + + let client = match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + }; + + let addr = &proxy.addr; + let linux = cfg!(target_os = "linux"); + let socks = addr.starts_with("socks://"); + + let client = if socks && linux { + match crate::client::Proxy::all(addr.replacen("socks://", "http://", 1)) { + Ok(p) => client.proxy(p), + Err(_) => return None, + } + } else { + match crate::client::Proxy::all(addr) { + Ok(p) => client.proxy(p), + Err(_) => return None, + } + }; + + #[cfg(feature = "spider_cloud")] + let client = if let Some(ref sc) = self.configuration.spider_cloud { + if sc.uses_proxy() { + match ( + crate::client::Proxy::all(&sc.proxy_url), + reqwest::header::HeaderValue::from_str(&format!("Bearer {}", sc.api_key)), + ) { + (Ok(proxy), Ok(auth_value)) => client.proxy(proxy.custom_http_auth(auth_value)), + _ => client, + } + } else { + client + } + } else { + client + }; + + let client = self.configure_http_client_cookies(client); + + let client = if crate::utils::connect::background_connect_threading() { + client.connector_layer(crate::utils::connect::BackgroundProcessorLayer::new()) + } else { + client + }; + + let client = match self.configuration.concurrency_limit { + Some(limit) => { + client.connector_layer(tower::limit::concurrency::ConcurrencyLimitLayer::new(limit)) + } + _ => client, + }; + + let client = + reqwest_middleware::ClientBuilder::new(unsafe { client.build().unwrap_unchecked() }); + + if self.configuration.cache { + let mut cache_options = HttpCacheOptions::default(); + + cache_options.cache_key = Some(Arc::new(|req: &http::request::Parts| { + let mut auth_token = None; + if let Some(auth) = req.headers.get("authorization") { + if let Ok(token) = auth.to_str() { + if !token.is_empty() { + auth_token = Some(token); + } + } + } + create_cache_key(req, Some(req.method.as_str()), auth_token) + })); + + Some( + client + .with(Cache(HttpCache { + mode: CacheMode::Default, + manager: CACACHE_MANAGER.clone(), + options: cache_options, + })) + .build(), + ) + } else { + Some(client.build()) + } + } + + /// Build rotated clients from the proxy list. Returns None if fewer than 2 proxies. + #[cfg(not(feature = "decentralized"))] + fn build_rotated_clients(&self) -> Option> { + let proxies = self.configuration.proxies.as_ref()?; + if proxies.len() < 2 { + return None; + } + let clients: Vec = proxies + .iter() + .filter_map(|proxy| self.build_single_proxy_client(proxy)) + .collect(); + if clients.len() < 2 { + return None; + } + Some(Arc::new(ClientRotator::new(clients))) + } + + /// Configure http client. + #[cfg(all(not(feature = "decentralized"), not(feature = "cache_request")))] + pub fn configure_http_client(&self) -> Client { + let client = self.configure_http_client_builder(); + // should unwrap using native-tls-alpn + unsafe { client.build().unwrap_unchecked() } + } + + /// Configure http client. + #[cfg(all(not(feature = "decentralized"), feature = "cache_request"))] + pub fn configure_http_client(&self) -> Client { + let client = self.configure_http_client_builder(); + client.build() + } + + /// Configure http client for decentralization. + #[cfg(all(feature = "decentralized", not(feature = "cache_request")))] + pub fn configure_http_client(&self) -> Client { + use reqwest::header::{HeaderMap, HeaderValue}; + + let mut headers = HeaderMap::new(); + + let policy = self.setup_redirect_policy(); + + let mut client = Client::builder() + .user_agent(match &self.configuration.user_agent { + Some(ua) => ua.as_str(), + _ => &get_ua(self.configuration.only_chrome_agent()), + }) + .redirect(policy) + .tcp_keepalive(Duration::from_millis(500)); + + let referer = if self.configuration.tld && self.configuration.subdomains { + 2 + } else if self.configuration.tld { + 2 + } else if self.configuration.subdomains { + 1 + } else { + 0 + }; + + if referer > 0 { + // use expected http headers for providers that drop invalid headers + headers.insert(reqwest::header::REFERER, HeaderValue::from(referer)); + } + + if let Some(h) = &self.configuration.headers { + headers.extend(h.inner().clone()); + } + + if let Some(domain_url) = self.get_absolute_path(None) { + let domain_url = domain_url.as_str(); + let domain_host = if domain_url.ends_with("/") { + &domain_url[0..domain_url.len() - 1] + } else { + domain_url + }; + if let Ok(value) = HeaderValue::from_str(domain_host) { + headers.insert(reqwest::header::HOST, value); + } + } + + for worker in WORKERS.iter() { + if let Ok(worker) = crate::client::Proxy::all(worker) { + client = client.proxy(worker); + } + } + + if !self.configuration.modify_headers && self.configuration.modify_http_client_headers { + if let Some(ua) = &self.configuration.user_agent { + crate::utils::header_utils::extend_headers( + &mut headers, + ua, + &self.configuration.headers, + &None, + &self.configuration.viewport, + &self.domain_parsed, + ); + } + } + + // should unwrap using native-tls-alpn + unsafe { + match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + } + .default_headers(headers) + .build() + .unwrap_unchecked() + } + } + + /// Configure http client for decentralization. + #[cfg(all(feature = "decentralized", feature = "cache_request"))] + pub fn configure_http_client(&mut self) -> Client { + use crate::utils::create_cache_key; + use reqwest::header::{HeaderMap, HeaderValue}; + use reqwest_middleware::ClientBuilder; + + let mut headers = HeaderMap::new(); + + let policy = self.setup_redirect_policy(); + + let mut client = reqwest::Client::builder() + .user_agent(match &self.configuration.user_agent { + Some(ua) => ua.as_str(), + _ => get_ua(self.configuration.only_chrome_agent()), + }) + .redirect(policy) + .tcp_keepalive(Duration::from_millis(500)); + + let referer = if self.configuration.tld && self.configuration.subdomains { + 2 + } else if self.configuration.tld { + 2 + } else if self.configuration.subdomains { + 1 + } else { + 0 + }; + + if referer > 0 { + // use expected http headers for providers that drop invalid headers + headers.insert(reqwest::header::REFERER, HeaderValue::from(referer)); + } + + if let Some(h) = &self.configuration.headers { + headers.extend(h.inner().clone()); + } + + if let Some(domain_url) = self.get_absolute_path(None) { + let domain_url = domain_url.as_str(); + let domain_host = if domain_url.ends_with("/") { + &domain_url[0..domain_url.len() - 1] + } else { + domain_url + }; + if let Ok(value) = HeaderValue::from_str(domain_host) { + headers.insert(reqwest::header::HOST, value); + } + } + + for worker in WORKERS.iter() { + if let Ok(worker) = crate::client::Proxy::all(worker) { + client = client.proxy(worker); + } + } + + let mut cache_options = HttpCacheOptions::default(); + + cache_options.cache_key = Some(Arc::new(|req: &http::request::Parts| { + let mut auth_token = None; + if let Some(auth) = req.headers.get("authorization") { + if let Ok(token) = auth.to_str() { + if !token.is_empty() { + auth_token = Some(token); + } + } + } + create_cache_key(req, Some(req.method.as_str()), auth_token) + })); + + if !self.configuration.modify_headers && self.configuration.modify_http_client_headers { + if let Some(ua) = &self.configuration.user_agent { + crate::utils::header_utils::extend_headers( + &mut headers, + ua, + &self.configuration.headers, + &None, + &self.configuration.viewport, + &self.domain_parsed, + ); + } + } + + let client = ClientBuilder::new(unsafe { + match &self.configuration.request_timeout { + Some(t) => client.timeout(*t), + _ => client, + } + .default_headers(headers) + .build() + .unwrap_unchecked() + }) + .with(Cache(HttpCache { + mode: CacheMode::Default, + manager: CACACHE_MANAGER.clone(), + options: cache_options, + })); + + client.build() + } + + /// Setup atomic controller. This does nothing without the 'control' feature flag enabled. + #[cfg(feature = "control")] + pub fn configure_handler(&self) -> Option<(Arc, tokio::task::JoinHandle<()>)> { + use crate::utils::{Handler, CONTROLLER}; + + if self.configuration.no_control_thread { + None + } else { + let c: Arc = Arc::new(AtomicI8::new(0)); + let handle = c.clone(); + let target_id = self.target_id(); + + let join_handle = crate::utils::spawn_task("control_handler", async move { + let mut l = CONTROLLER.read().await.1.to_owned(); + + while l.changed().await.is_ok() { + let n = &*l.borrow(); + let (target, rest) = n; + + if target_id.eq_ignore_ascii_case(target) { + if rest == &Handler::Resume { + c.store(0, Ordering::Relaxed); + } + if rest == &Handler::Pause { + c.store(1, Ordering::Relaxed); + } + if rest == &Handler::Shutdown { + c.store(2, Ordering::Relaxed); + } + } + } + }); + + Some((handle, join_handle)) + } + } + + #[cfg(not(feature = "control"))] + /// Setup atomic controller. This does nothing without the 'control' feature flag enabled. + pub fn configure_handler(&self) -> Option<(Arc, tokio::task::JoinHandle<()>)> { + None + } + + /// Setup interception for chrome request. + #[cfg(all(feature = "chrome", feature = "chrome_intercept"))] + pub async fn setup_chrome_interception( + &self, + page: &chromiumoxide::Page, + ) -> Option> { + crate::features::chrome::setup_chrome_interception_base( + page, + self.configuration.chrome_intercept.enabled, + &self.configuration.auth_challenge_response, + self.configuration.chrome_intercept.block_visuals, + self.url.inner(), + ) + .await + } + + /// Setup interception for chrome request + #[cfg(all(feature = "chrome", not(feature = "chrome_intercept")))] + pub async fn setup_chrome_interception( + &self, + _chrome_page: &chromiumoxide::Page, + ) -> Option> { + None + } + + /// Setup selectors for handling link targets. + pub fn setup_selectors(&self) -> RelativeSelectors { + setup_website_selectors( + self.get_url().inner(), + AllowedDomainTypes::new(self.configuration.subdomains, self.configuration.tld), + ) + } + + /// Base configuration setup. + pub fn setup_base(&mut self) -> (Client, Option<(Arc, tokio::task::JoinHandle<()>)>) { + self.determine_limits(); + self.setup_disk(); + self.configure_headers(); + + crate::utils::connect::init_background_runtime(); + + let client = match self.client.take() { + Some(client) => client, + _ => self.configure_http_client(), + }; + + #[cfg(not(feature = "decentralized"))] + { + self.client_rotator = self.build_rotated_clients(); + } + + (client, self.configure_handler()) + } + + /// Setup config for crawl. + pub async fn setup( + &mut self, + ) -> (Client, Option<(Arc, tokio::task::JoinHandle<()>)>) { + let setup = self.setup_base(); + if self.status != CrawlStatus::Active { + self.clear_all().await; + } else { + self.skip_initial = !self.extra_links.is_empty(); + } + self.configure_robots_parser(&setup.0).await; + setup + } + + /// Setup shared concurrent configs. + pub fn setup_crawl( + &self, + ) -> ( + std::pin::Pin>, + std::pin::Pin>, + ) { + let interval = Box::pin(tokio::time::interval(Duration::from_millis(10))); + let throttle = Box::pin(self.get_delay()); + + (interval, throttle) + } + + /// Get all the expanded links. + #[cfg(feature = "glob")] + pub fn get_expanded_links(&self, domain_name: &str) -> Vec { + let mut expanded = crate::features::glob::expand_url(domain_name); + + if expanded.is_empty() { + if let Some(u) = self.get_absolute_path(Some(domain_name)) { + expanded.push(u.as_str().into()); + } + }; + + expanded + } + + /// Set the initial crawl status by page output. + pub fn set_crawl_initial_status( + &mut self, + page: &crate::page::Page, + links: &HashSet, + ) { + use crate::utils::{detect_open_resty_forbidden, APACHE_FORBIDDEN}; + + if page.status_code == reqwest::StatusCode::FORBIDDEN && links.is_empty() { + if is_safe_javascript_challenge(page) { + self.website_meta_info = WebsiteMetaInfo::RequiresJavascript; + } else if page.get_html_bytes_u8() == *APACHE_FORBIDDEN { + self.website_meta_info = WebsiteMetaInfo::Apache403; + } else if detect_open_resty_forbidden(page.get_html_bytes_u8()) { + self.website_meta_info = WebsiteMetaInfo::OpenResty403; + } + self.status = CrawlStatus::Blocked; + } else if page.status_code == reqwest::StatusCode::TOO_MANY_REQUESTS { + self.status = CrawlStatus::RateLimited; + } else if (page.status_code == *UNKNOWN_STATUS_ERROR + || page.status_code == *CHROME_UNKNOWN_STATUS_ERROR) + && page.is_empty() + { + self.status = CrawlStatus::ConnectError; + } else if page.status_code.is_server_error() { + self.status = CrawlStatus::ServerError; + } else if page.is_empty() { + self.status = CrawlStatus::Empty; + } + } + + /// Expand links for crawl base establish using a **command-based fetch**. + #[cfg(feature = "cmd")] + pub async fn _crawl_establish_cmd( + &mut self, + cmd: std::path::PathBuf, + cmd_args: Vec, + base: &mut RelativeSelectors, + _ssg_build: bool, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + + if !self + .is_allowed_default(self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + return HashSet::new(); + } + + let url = self.url.inner(); + + let mut links: HashSet = HashSet::new(); + let mut links_ssg = HashSet::new(); + let mut links_pages = if self.configuration.return_page_links { + Some(HashSet::new()) + } else { + None + }; + + let mut page_links_settings = + PageLinkBuildSettings::new(true, self.configuration.full_resources); + page_links_settings.subdomains = self.configuration.subdomains; + page_links_settings.tld = self.configuration.tld; + page_links_settings.normalize = self.configuration.normalize; + + let mut domain_parsed = self.domain_parsed.take(); + + let mut retry_count = self.configuration.retry; + let mut last_err: Option = None; + + let build_error_page = |status: StatusCode, _err: std::io::Error| { + let mut p = Page::default(); + p.url = url.to_string(); + p.status_code = status; + #[cfg(not(feature = "page_error_status_details"))] + { + p.error_status = Some(_err.to_string()); + } + p + }; + + let mut page: Page = loop { + let bytes = match Self::run_via_cmd(&cmd, &cmd_args, url).await { + Ok(b) => { + if b.is_empty() { + last_err = Some(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "cmd returned empty stdout", + )); + None + } else { + Some(b) + } + } + Err(e) => { + last_err = Some(e); + None + } + }; + + if let Some(bytes) = bytes.as_deref() { + let mut domain_parsed_out = None; + + let page = Page::new_page_streaming_from_bytes( + url, + bytes, + base, + &self.configuration.external_domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, + &mut domain_parsed_out, + &mut links_pages, + ) + .await; + + if self.domain_parsed.is_none() { + if let Some(mut dp) = domain_parsed.take() { + convert_abs_url(&mut dp); + self.domain_parsed.replace(dp); + } else if let Some(mut dp) = domain_parsed_out.take() { + convert_abs_url(&mut dp); + self.domain_parsed.replace(dp); + } + } else if self.domain_parsed.is_none() { + self.domain_parsed = domain_parsed_out; + } + + if page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } else { + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + } + continue; + } + + break page; + } + + if retry_count == 0 { + let err = last_err + .take() + .unwrap_or_else(|| std::io::Error::other("cmd fetch failed (unknown error)")); + break build_error_page(StatusCode::BAD_GATEWAY, err); + } + + retry_count -= 1; + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + }; + + if page.get_html_bytes_u8().starts_with(b" cb(*self.url.clone(), None).0, + _ => *self.url.clone(), + }; + self.insert_link(url_ci).await; + + if self.configuration.return_page_links { + page.page_links = links_pages + .filter(|pages: &HashSet| !pages.is_empty()) + .map(Box::new); + } + + links.extend(links_ssg); + + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.initial_anti_bot_tech = page.anti_bot_tech; + self.initial_page_should_retry = page.should_retry; + self.initial_page_waf_check = page.waf_check; + + self.set_crawl_initial_status(&page, &links); + + if let Some(ref cb) = self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + return Default::default(); + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + + links + } + + /// Expand links for crawl base establish. + #[cfg(not(feature = "glob"))] + pub async fn _crawl_establish( + &mut self, + client: &Client, + base: &mut RelativeSelectors, + _: bool, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + + if self + .is_allowed_default(self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let url = self.url.inner(); + + let mut links: HashSet = HashSet::new(); + let mut links_ssg = HashSet::new(); + let mut links_pages = if self.configuration.return_page_links { + Some(HashSet::new()) + } else { + None + }; + let mut page_links_settings = + PageLinkBuildSettings::new(true, self.configuration.full_resources); + + page_links_settings.subdomains = self.configuration.subdomains; + page_links_settings.tld = self.configuration.tld; + page_links_settings.normalize = self.configuration.normalize; + + let mut domain_parsed = self.domain_parsed.take(); + + let mut page = if let Some(mut seeded_page) = self.build_seed_page() { + // Extract links and metadata from seeded HTML content if not binary + #[cfg(not(feature = "decentralized"))] + { + let html_bytes = seeded_page.get_html_bytes_u8(); + if !html_bytes.is_empty() && !auto_encoder::is_binary_file(html_bytes) { + let html = seeded_page.get_html(); + let extracted_links: HashSet = seeded_page + .links_stream_base_ssg(base, &html, client, &self.domain_parsed) + .await; + links.extend(extracted_links); + } + } + seeded_page + } else { + Page::new_page_streaming( + url, + client, + false, + base, + &self.configuration.external_domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, // original domain + &mut self.domain_parsed, + &mut links_pages, + ) + .await + }; + + if page.get_html_bytes_u8().starts_with(b" 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + let mut domain_parsed_clone = self.domain_parsed.clone(); + + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + page.clone_from( + &Page::new_page_streaming( + url, + client, + false, + base, + domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, + &mut domain_parsed_clone, + &mut links_pages, + ) + .await, + ); + }) + .await + { + log::info!("backoff gateway timeout exceeded {elasped}"); + } + + self.domain_parsed = domain_parsed_clone; + } else { + page.clone_from( + &Page::new_page_streaming( + url, + client, + false, + base, + &self.configuration.external_domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, + &mut self.domain_parsed, + &mut links_pages, + ) + .await, + ); + } + } + + emit_log(url); + + if let Some(signature) = page.signature { + if !self.is_signature_allowed(signature).await { + return Default::default(); + } + self.insert_signature(signature).await; + } + + let url = match &self.on_link_find_callback { + Some(cb) => cb(*self.url.clone(), None).0, + _ => *self.url.clone(), + }; + + self.insert_link(url).await; + + if self.configuration.return_page_links { + page.page_links = links_pages + .filter(|pages: &HashSet| !pages.is_empty()) + .map(Box::new); + } + + links.extend(links_ssg); + + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.initial_anti_bot_tech = page.anti_bot_tech; + self.initial_page_should_retry = page.should_retry; + self.initial_page_waf_check = page.waf_check; + + self.set_crawl_initial_status(&page, &links); + + if let Some(ref cb) = self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + return Default::default(); + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + + links + } else { + HashSet::new() + } + } + + /// Run `cmd` and return stdout bytes. + #[cfg(feature = "cmd")] + pub async fn run_via_cmd( + cmd: &std::path::Path, + fixed_args: &[String], + url: &str, + ) -> std::io::Result> { + use tokio::process::Command; + let mut args: Vec = Vec::with_capacity(fixed_args.len() + 1); + let mut used_placeholder = false; + + for a in fixed_args { + if a.contains("{url}") { + used_placeholder = true; + args.push(a.replace("{url}", url)); + } else { + args.push(a.clone()); + } + } + + if !used_placeholder { + args.push(url.to_string()); + } + + let out = Command::new(cmd) + .args(&args) + .kill_on_drop(true) + .output() + .await?; + + if !out.status.success() { + let code = out.status.code().unwrap_or(-1); + let stderr = String::from_utf8_lossy(&out.stderr); + + return Err(std::io::Error::other(format!( + "cmd exit={code} stderr={stderr}" + ))); + } + + Ok(out.stdout) + } + + /// Start to crawl website concurrently using a cmd executable. + /// - `cmd` is the executable (absolute preferred) + /// - `cmd_args` are fixed args; can include "{url}" placeholder, otherwise url is appended. + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + #[cfg(feature = "cmd")] + pub async fn crawl_concurrent_cmd( + &mut self, + cmd: std::path::PathBuf, + cmd_args: Vec, + handle: &Option>, + ) { + self.start(); + self.status = CrawlStatus::Active; + + let mut selector: ( + CompactString, + smallvec::SmallVec<[CompactString; 2]>, + CompactString, + ) = self.setup_selectors(); + + if self.single_page() { + let mut links: HashSet = HashSet::new(); + let mut links_pages: Option> = + if self.configuration.return_page_links { + Some(HashSet::new()) + } else { + None + }; + + let mut relative_selectors = selector; + let mut domain_parsed = None; + + let target = self + .domain_parsed + .as_ref() + .map(|u| u.as_str()) + .unwrap_or(self.get_url()); + + let bytes = match Self::run_via_cmd(&cmd, &cmd_args, target).await { + Ok(b) => b, + Err(_err) => { + let mut page = Page::default(); + page.url = target.to_string(); + page.status_code = StatusCode::BAD_GATEWAY; + #[cfg(not(feature = "page_error_status_details"))] + { + page.error_status = Some(_err.to_string()); + } + channel_send_page(&self.channel, page, &self.channel_guard); + return; + } + }; + + let page = Page::new_page_streaming_from_bytes( + target, + &bytes, + &mut relative_selectors, + &self.configuration.external_domains_caseless, + &PageLinkBuildSettings::new_full( + false, + self.configuration.full_resources, + self.configuration.subdomains, + self.configuration.tld, + self.configuration.normalize, + ), + &mut links, + None, + &self.domain_parsed, + &mut domain_parsed, + &mut links_pages, + ) + .await; + + channel_send_page(&self.channel, page, &self.channel_guard); + return; + } + + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let return_page_links = self.configuration.return_page_links; + let full_resources = self.configuration.full_resources; + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let (mut interval, throttle) = self.setup_crawl(); + let mut links: HashSet = self.drain_extra_links().collect(); + + links.extend( + self._crawl_establish_cmd(cmd.clone(), cmd_args.clone(), &mut selector, false) + .await, + ); + + self.configuration.configure_allowlist(); + let semaphore = self.setup_semaphore(); + + let shared = Arc::new(( + cmd, + cmd_args, + selector, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + self.configuration.retry, + return_page_links, + PageLinkBuildSettings::new_full( + false, + full_resources, + self.configuration.subdomains, + self.configuration.tld, + self.configuration.normalize, + ), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + )); + + let mut set: JoinSet<(HashSet, Option)> = JoinSet::new(); + + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter(links.drain().collect::>()); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = get_semaphore(&semaphore, !self.configuration.shared_queue).await; + + tokio::select! { + biased; + + Some(link) = stream.next(), + if semaphore.available_permits() > 0 + && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => + { + if !self.handle_process(handle, &mut interval, async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }).await { + while let Some(links) = stream.next().await { + self.extra_links.insert(links); + } + break 'outer; + } + + let allowed = self.is_allowed(&link); + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + self.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + spawn_set("page_fetch_cmd", &mut set, async move { + let link_result = match &shared.10 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let mut out_links: HashSet = HashSet::new(); + let mut links_pages = if shared.7 { Some(HashSet::new()) } else { None }; + + let mut relative_selectors = shared.2.clone(); + let mut r_settings = shared.8; + r_settings.ssg_build = true; + + let target_url = link_result.0.as_ref(); + + // Run cmd -> bytes with retry + let mut retry_count = shared.6; + let mut last_err: Option = None; + + let bytes = loop { + match Self::run_via_cmd(&shared.0, &shared.1, target_url).await { + Ok(b) if !b.is_empty() => break Some(b), + Ok(_) => { + last_err = Some(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "cmd returned empty stdout", + )); + } + Err(e) => { + last_err = Some(e); + } + } + + if retry_count == 0 { break None; } + retry_count -= 1; + + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + }; + + let mut domain_parsed = None; + + let mut page = if let Some(bytes) = bytes { + Page::new_page_streaming_from_bytes( + target_url, + &bytes, + &mut relative_selectors, + &shared.4, + &r_settings, + &mut out_links, + None, + &shared.9, + &mut domain_parsed, + &mut links_pages, + ).await + } else { + // Build an error page + let mut p = Page::default(); + p.url = target_url.to_string(); + p.status_code = StatusCode::BAD_GATEWAY; + if let Some(_e) = last_err { + #[cfg(not(feature = "page_error_status_details"))] + { + p.error_status = Some(_e.to_string()); + } + } + p + }; + + if shared.7 { + page.page_links = links_pages + .filter(|pages| !pages.is_empty()) + .map(Box::new); + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.3, page, &shared.5); + drop(permit); + return Default::default(); + } + } + + let signature = page.signature; + channel_send_page(&shared.3, page, &shared.5); + drop(permit); + + (out_links, signature) + }); + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + }, + + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if self.is_signature_allowed(signature).await { + self.insert_signature(signature).await; + self.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + self.links_visited.extend_links(&mut links, res.0); + } + } + } else { + break; + } + } + + else => break, + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if (links.is_empty() && set.is_empty()) || exceeded_budget { + if exceeded_budget { + while let Some(links) = stream.next().await { + self.extra_links.insert(links); + } + while let Some(links) = set.join_next().await { + if let Ok(links) = links { + self.extra_links.extend(links.0); + } + } + } + break 'outer; + } + } + + self.subscription_guard().await; + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + if !links.is_empty() { + self.extra_links.extend(links); + } + } + + /// Build a page from a seed. + #[allow(dead_code)] + fn build_seed_page(&self) -> Option { + if let Some(seeded_html) = self.get_seeded_html() { + if crate::utils::is_cacheable_body_empty(seeded_html.as_bytes()) { + return None; + } + let mut page_response = PageResponse::default(); + page_response.content = Some(Box::new(seeded_html.as_bytes().to_vec())); + Some(build(self.url.inner(), page_response)) + } else { + None + } + } + + /// Expand links for crawl. + #[cfg(all( + not(feature = "decentralized"), + feature = "chrome", + not(feature = "glob") + ))] + pub async fn crawl_establish( + &mut self, + client: &Client, + base: &mut RelativeSelectors, + _: bool, + chrome_page: &chromiumoxide::Page, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + + if self + .is_allowed_default(self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events(chrome_page, &self.configuration), + self.setup_chrome_interception(chrome_page) + ); + + let mut page = if let Some(seeded_html) = self.get_seeded_html() { + Page::new_seeded( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + Some(seeded_html.clone()), + Some(&self.cookie_jar), + &self.configuration.remote_multimodal, + ) + .await + } else { + Page::new( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await + }; + + let mut retry_count = self.configuration.retry; + + if let Some(final_redirect_destination) = &page.final_redirect_destination { + if final_redirect_destination == "chrome-error://chromewebdata/" + && page.status_code.is_success() + && page.is_empty() + && self.configuration.proxies.is_some() + { + page.error_status = Some("Invalid proxy configuration.".into()); + page.should_retry = true; + page.status_code = *crate::page::CHROME_UNKNOWN_STATUS_ERROR; + } + } + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let next_page = Page::new( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + page.clone_from(&next_page); + }) + .await + { + log::warn!("backoff timeout {elasped}"); + } + } else { + let next_page = Page::new( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + page.clone_from(&next_page); + } + + // check the page again for final. + if let Some(final_redirect_destination) = &page.final_redirect_destination { + if final_redirect_destination == "chrome-error://chromewebdata/" + && page.status_code.is_success() + && page.is_empty() + && self.configuration.proxies.is_some() + { + page.error_status = Some("Invalid proxy configuration.".into()); + page.should_retry = true; + page.status_code = *crate::page::CHROME_UNKNOWN_STATUS_ERROR; + } + } + } + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = + tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if let Some(domain) = &page.final_redirect_destination { + let domain: Box = CaseInsensitiveString::new(&domain).into(); + let prior_domain = self.domain_parsed.take(); + self.domain_parsed = parse_absolute_url(&domain); + self.url = domain; + + let s = self.setup_selectors(); + base.0 = s.0; + base.1 = s.1; + + if let Some(pdname) = prior_domain { + if let Some(dname) = pdname.host_str() { + base.2 = dname.into(); + } + } + } + + emit_log(self.url.inner()); + + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + + let url = match &self.on_link_find_callback { + Some(cb) => cb(*self.url.clone(), None).0, + _ => *self.url.clone(), + }; + + self.insert_link(url).await; + + // setup link tracking. + if self.configuration.return_page_links && page.page_links.is_none() { + page.page_links = Some(Box::default()); + } + + let xml_file = page.get_html_bytes_u8().starts_with(b", + chrome_page: &chromiumoxide::Page, + ) -> HashSet { + if self + .is_allowed_default(self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events(chrome_page, &self.configuration), + self.setup_chrome_interception(chrome_page) + ); + + let mut page = Page::new( + url.unwrap_or(self.url.inner()), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + + let mut retry_count = self.configuration.retry; + + if let Some(final_redirect_destination) = &page.final_redirect_destination { + if final_redirect_destination == "chrome-error://chromewebdata/" + && page.status_code.is_success() + && page.is_empty() + && self.configuration.proxies.is_some() + { + page.error_status = Some("Invalid proxy configuration.".into()); + page.should_retry = true; + page.status_code = *crate::page::CHROME_UNKNOWN_STATUS_ERROR; + } + } + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let next_page = Page::new( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + page.clone_from(&next_page); + }) + .await + { + log::warn!("backoff timeout {elasped}"); + } + } else { + let next_page = Page::new( + self.url.inner(), + client, + chrome_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + page.clone_from(&next_page); + } + + // check the page again for final. + if let Some(final_redirect_destination) = &page.final_redirect_destination { + if final_redirect_destination == "chrome-error://chromewebdata/" + && page.status_code.is_success() + && page.is_empty() + && self.configuration.proxies.is_some() + { + page.error_status = Some("Invalid proxy configuration.".into()); + page.should_retry = true; + page.status_code = *crate::page::CHROME_UNKNOWN_STATUS_ERROR; + } + } + } + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = + tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if let Some(domain) = &page.final_redirect_destination { + let domain: Box = CaseInsensitiveString::new(&domain).into(); + let s = self.setup_selectors(); + + base.0 = s.0; + base.1 = s.1; + + if let Some(pdname) = parse_absolute_url(&domain) { + if let Some(dname) = pdname.host_str() { + base.2 = dname.into(); + } + } + } + + emit_log(self.url.inner()); + + if self.configuration.return_page_links && page.page_links.is_none() { + page.page_links = Some(Box::default()); + } + + let xml_file = page.get_html_bytes_u8().starts_with(b", + driver: &std::sync::Arc, + ) -> HashSet { + if self + .is_allowed_default(self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let timeout = self + .configuration + .webdriver_config + .as_ref() + .and_then(|c| c.timeout); + + // Setup stealth events + crate::features::webdriver::setup_driver_events(driver, &self.configuration).await; + + let mut page = + Page::new_page_webdriver(url.unwrap_or(self.url.inner()), driver, timeout).await; + + let mut retry_count = self.configuration.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout_duration) = page.get_timeout() { + tokio::time::sleep(timeout_duration).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elapsed) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let next_page = + Page::new_page_webdriver(self.url.inner(), driver, timeout).await; + page.clone_from(&next_page); + }) + .await + { + log::warn!("backoff timeout {elapsed}"); + } + } else { + let next_page = + Page::new_page_webdriver(self.url.inner(), driver, timeout).await; + page.clone_from(&next_page); + } + } + + if let Some(domain) = &page.final_redirect_destination { + let domain: Box = CaseInsensitiveString::new(&domain).into(); + let s = self.setup_selectors(); + + base.0 = s.0; + base.1 = s.1; + + if let Some(pdname) = parse_absolute_url(&domain) { + if let Some(dname) = pdname.host_str() { + base.2 = dname.into(); + } + } + } + + emit_log(self.url.inner()); + + if self.configuration.return_page_links && page.page_links.is_none() { + page.page_links = Some(Box::default()); + } + + let xml_file = page.get_html_bytes_u8().starts_with(b"), + http_worker: bool, + ) -> HashSet { + // base_domain name passed here is for primary url determination and not subdomain.tld placement + let links: HashSet = if self + .is_allowed_default(&self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let link = self.url.inner(); + + let mut page = Page::new_page_with_cache( + &if http_worker && link.starts_with("https") { + link.replacen("https", "http", 1) + } else { + link.to_string() + }, + &client, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + ) + .await; + + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + + self.insert_link(match &self.on_link_find_callback { + Some(cb) => cb(*self.url.to_owned(), None).0, + _ => *self.url.to_owned(), + }) + .await; + + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.initial_anti_bot_tech = page.anti_bot_tech; + self.initial_page_should_retry = page.should_retry; + self.initial_page_waf_check = page.waf_check; + + // todo: pass full links to the worker to return. + if self.configuration.return_page_links { + page.page_links = Some(page.links.clone().into()); + } + + let links = HashSet::from(page.links.clone()); + + self.set_crawl_initial_status(&page, &links); + + channel_send_page(&self.channel, page, &self.channel_guard); + + links + } else { + HashSet::new() + }; + + links + } + + /// Expand links for crawl. + #[cfg(all(feature = "glob", feature = "decentralized"))] + pub async fn crawl_establish( + &mut self, + client: &Client, + _: &(CompactString, smallvec::SmallVec<[CompactString; 2]>), + http_worker: bool, + ) -> HashSet { + let mut links: HashSet = HashSet::new(); + let expanded = self.get_expanded_links(self.url.inner().as_str()); + self.configuration.configure_allowlist(); + + for link in expanded { + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + let mut page = Page::new_page_with_cache( + &if http_worker && link.as_ref().starts_with("https") { + link.inner().replacen("https", "http", 1).to_string() + } else { + link.inner().to_string() + }, + client, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + ) + .await; + + let u = page.get_url(); + let u = if u.is_empty() { link } else { u.into() }; + + let link_result = match &self.on_link_find_callback { + Some(cb) => cb(u, None), + _ => (u, None), + }; + + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + + self.insert_link(link_result.0).await; + + if self.configuration.return_page_links { + page.page_links = Some(Default::default()); + } + + channel_send_page(&self.channel, page.clone(), &self.channel_guard); + + let page_links = page.links; + + links.extend(page_links); + } + + links + } + + /// Expand links for crawl. + #[cfg(all(feature = "glob", feature = "chrome", not(feature = "decentralized")))] + pub async fn crawl_establish( + &mut self, + client: &Client, + base: &mut RelativeSelectors, + _: bool, + page: &chromiumoxide::Page, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + let mut links: HashSet = HashSet::new(); + let expanded = self.get_expanded_links(&self.url.inner().as_str()); + self.configuration.configure_allowlist(); + + for link in expanded { + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + let mut page = Page::new( + &link.inner().as_str(), + &client, + &page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + + let u = page.get_url(); + let u = if u.is_empty() { link } else { u.into() }; + + let link_result = match &self.on_link_find_callback { + Some(cb) => cb(u, None), + _ => (u, None), + }; + + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + + self.insert_link(link_result.0).await; + + if self.configuration.return_page_links { + page.page_links = Some(Default::default()); + let next_links = HashSet::from(page.links(&base, &self.domain_parsed).await); + + channel_send_page(&self.channel, page.clone(), &self.channel_guard); + + links.extend(next_links); + } else { + channel_send_page(&self.channel, page.clone(), &self.channel_guard); + let next_links = HashSet::from(page.links(&base, &self.domain_parsed).await); + + links.extend(next_links); + } + } + + links + } + + /// Expand links for crawl. + #[cfg(feature = "glob")] + async fn _crawl_establish( + &mut self, + client: &Client, + base: &mut RelativeSelectors, + _: bool, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + let mut links: HashSet = HashSet::new(); + let domain_name = self.url.inner(); + let expanded = self.get_expanded_links(domain_name.as_str()); + + self.configuration.configure_allowlist(); + + for url in expanded { + #[cfg(feature = "regex")] + let url_ref: &CaseInsensitiveString = &url; + #[cfg(not(feature = "regex"))] + let url_ref: &CompactString = url.inner(); + if self + .is_allowed_default(url_ref) + .eq(&ProcessLinkStatus::Allowed) + { + let mut links_ssg = HashSet::new(); + let mut links_pages = if self.configuration.return_page_links { + Some(HashSet::new()) + } else { + None + }; + let mut page_links_settings = + PageLinkBuildSettings::new(true, self.configuration.full_resources); + + page_links_settings.subdomains = self.configuration.subdomains; + page_links_settings.tld = self.configuration.tld; + page_links_settings.normalize = self.configuration.normalize; + + let mut domain_parsed = self.domain_parsed.take(); + + let mut page = Page::new_page_streaming( + &url, + client, + false, + base, + &self.configuration.external_domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, // original domain + &mut self.domain_parsed, + &mut links_pages, + ) + .await; + + if self.domain_parsed.is_none() { + if let Some(mut domain_parsed) = domain_parsed.take() { + convert_abs_url(&mut domain_parsed); + self.domain_parsed.replace(domain_parsed); + } + } + + let mut retry_count = self.configuration.retry; + let domains_caseless = &self.configuration.external_domains_caseless; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + let mut domain_parsed_clone = self.domain_parsed.clone(); + + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + page.clone_from( + &Page::new_page_streaming( + &url, + client, + false, + base, + domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, + &mut domain_parsed_clone, + &mut links_pages, + ) + .await, + ); + }) + .await + { + log::info!("backoff gateway timeout exceeded {elasped}"); + } + + self.domain_parsed = domain_parsed_clone; + } else { + page.clone_from( + &Page::new_page_streaming( + &url, + client, + false, + base, + &self.configuration.external_domains_caseless, + &page_links_settings, + &mut links, + Some(&mut links_ssg), + &domain_parsed, + &mut self.domain_parsed, + &mut links_pages, + ) + .await, + ); + } + } + + emit_log(&url); + + if let Some(signature) = page.signature { + if !self.is_signature_allowed(signature).await { + return Default::default(); + } + self.insert_signature(signature).await; + } + + self.insert_link( + self.on_link_find_callback + .as_ref() + .map(|cb| cb(*self.url.clone(), None).0) + .unwrap_or_else(|| *self.url.clone()), + ) + .await; + + if self.configuration.return_page_links { + page.page_links = links_pages.filter(|pages| !pages.is_empty()).map(Box::new); + } + + links.extend(links_ssg); + + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.initial_anti_bot_tech = page.anti_bot_tech; + self.initial_page_should_retry = page.should_retry; + self.initial_page_waf_check = page.waf_check; + + self.set_crawl_initial_status(&page, &links); + + if let Some(ref cb) = self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + return Default::default(); + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + } + } + + links + } + + /// Expand links for crawl. + #[cfg(all(not(feature = "decentralized"), feature = "smart"))] + pub async fn crawl_establish_smart( + &mut self, + client: &Client, + mut base: &mut RelativeSelectors, + browser: &crate::features::chrome::OnceBrowser, + ) -> HashSet { + if self.skip_initial { + return Default::default(); + } + + let links: HashSet = if self + .is_allowed_default(&self.get_base_link()) + .eq(&ProcessLinkStatus::Allowed) + { + let url = self.url.inner(); + + let mut page = if let Some(seeded_page) = self.build_seed_page() { + seeded_page + } else { + Page::new_page_with_cache( + &url, + &client, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + ) + .await + }; + + let mut retry_count = self.configuration.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + let client_error = page.status_code.is_client_error(); + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + if retry_count.is_power_of_two() { + Website::render_chrome_page( + &self.configuration, + client, + &mut page, + url, + &self.domain_parsed, + browser, + ) + .await; + } else { + let next_page = Page::new_page_with_cache( + url, + &client, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + ) + .await; + page.clone_from(&next_page); + }; + }) + .await + { + log::warn!("backoff timeout {elasped}"); + } + } else { + if retry_count.is_power_of_two() || client_error { + Website::render_chrome_page( + &self.configuration, + client, + &mut page, + url, + &self.domain_parsed, + browser, + ) + .await + } else { + page.clone_from( + &Page::new_page_with_cache( + url, + &client, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + ) + .await, + ); + } + } + } + + let (page_links, bytes_transferred): (HashSet, Option) = + page.smart_links( + &base, + &self.configuration, + &self.domain_parsed, + &browser, + Some(&self.cookie_jar), + ) + .await; + + if let Some(domain) = &page.final_redirect_destination { + let prior_domain = self.domain_parsed.take(); + crate::utils::modify_selectors( + &prior_domain, + domain, + &mut self.domain_parsed, + &mut self.url, + &mut base, + AllowedDomainTypes::new(self.configuration.subdomains, self.configuration.tld), + ); + } + + emit_log(&self.url.inner()); + + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + + self.insert_link( + self.on_link_find_callback + .as_ref() + .map(|cb| cb(*self.url.clone(), None).0) + .unwrap_or_else(|| *self.url.clone()), + ) + .await; + + let links = if !page_links.is_empty() { + page_links + } else { + Default::default() + }; + + page.bytes_transferred = bytes_transferred; + + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.initial_anti_bot_tech = page.anti_bot_tech; + self.initial_page_should_retry = page.should_retry; + self.initial_page_waf_check = page.waf_check; + + self.set_crawl_initial_status(&page, &links); + + if self.configuration.return_page_links { + page.page_links = if links.is_empty() { + None + } else { + Some(Box::new(links.clone())) + }; + } + + if let Some(cb) = &mut self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + return Default::default(); + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + + links + } else { + HashSet::new() + }; + + links + } + + /// fetch the page with chrome + #[cfg(all(not(feature = "decentralized"), feature = "smart"))] + pub async fn render_chrome_page( + config: &Configuration, + client: &Client, + page: &mut Page, + url: &str, + base: &Option>, + browser: &crate::features::chrome::OnceBrowser, + ) { + if let Some(browser_controller) = browser + .get_or_init(|| crate::website::Website::setup_browser_base(&config, &base, None)) + .await + { + if let Ok(chrome_page) = crate::features::chrome::attempt_navigation( + "about:blank", + &browser_controller.browser.0, + &config.request_timeout, + &browser_controller.browser.2, + &config.viewport, + ) + .await + { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events(&chrome_page, &config), + crate::features::chrome::setup_chrome_interception_base( + &chrome_page, + config.chrome_intercept.enabled, + &config.auth_challenge_response, + config.chrome_intercept.block_visuals, + &url, + ) + ); + + let next_page = Page::new( + &url, + &client, + &chrome_page, + &config.wait_for, + &config.screenshot, + false, // we use the initial about:blank page. + &config.openai_config, + &config.execution_scripts, + &config.automation_scripts, + &config.viewport, + &config.request_timeout, + &config.track_events, + config.referer.clone(), + config.max_page_bytes, + config.get_cache_options(), + &config.cache_policy, + &config.remote_multimodal, + ) + .await; + + page.clone_from(&next_page); + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = + tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + } + } + } + + /// Set the crawl status depending on crawl state. The crawl that only changes if the state is Start or Active. + pub fn set_crawl_status(&mut self) { + if self.status == CrawlStatus::Start || self.status == CrawlStatus::Active { + self.status = if self.domain_parsed.is_none() { + CrawlStatus::Invalid + } else { + CrawlStatus::Idle + }; + } + } + + /// Setup the Semaphore for the crawl. + pub fn setup_semaphore(&self) -> Arc { + if self.configuration.shared_queue { + SEM_SHARED.clone() + } else { + Arc::new(Semaphore::const_new( + self.configuration + .concurrency_limit + .unwrap_or(*DEFAULT_PERMITS), + )) + } + } + + /// Fast path: serve single-page crawl from cache, bypassing ALL heavy setup. + /// No browser, no HTTP client, no robots.txt. Returns true on cache hit. + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + async fn try_cache_shortcircuit(&mut self) -> bool { + use crate::utils::{cache_skip_browser, get_cached_url}; + + // Ensure budget is configured so single_page() works before setup() + self.configuration.configure_budget(); + + if !self.single_page() { + return false; + } + + let cache_options = self.configuration.get_cache_options(); + if !cache_skip_browser(&cache_options) { + return false; + } + + let target_url = self.url.inner().to_string(); + + if let Some(html) = get_cached_url( + &target_url, + cache_options.as_ref(), + &self.configuration.cache_policy, + ) + .await + { + self.status = CrawlStatus::Active; + let page_response = crate::utils::build_cached_html_page_response(&target_url, &html); + let page = build(&target_url, page_response); + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + self.links_visited + .insert(CaseInsensitiveString::from(target_url.as_str())); + channel_send_page(&self.channel, page, &self.channel_guard); + self.subscription_guard().await; + return true; + } + + false + } + + /// No-op stub when cache features not enabled. + #[cfg(not(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + )))] + async fn try_cache_shortcircuit(&mut self) -> bool { + false + } + + /// Cache-only crawl phase: process initial URL + follow links from cache. + /// Returns `true` if all reachable pages were served from cache (Chrome/HTTP can be skipped). + /// Any cache-miss links are left in `self.extra_links` for the subsequent Chrome/HTTP phase. + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + async fn crawl_cache_phase(&mut self, _client: &Client) -> bool { + use crate::utils::{build_cached_html_page_response, cache_skip_browser, get_cached_url}; + + let cache_options = self.configuration.get_cache_options(); + if !cache_skip_browser(&cache_options) { + return false; + } + + self.configuration.configure_budget(); + self.configuration.configure_allowlist(); + + let target_url = self.url.inner().to_string(); + + // Try initial URL from cache + let html = match get_cached_url( + &target_url, + cache_options.as_ref(), + &self.configuration.cache_policy, + ) + .await + { + Some(h) => h, + None => return false, // Cache miss on initial URL — need Chrome/HTTP + }; + + self.status = CrawlStatus::Active; + let selectors = self.setup_selectors(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let normalize = self.configuration.normalize; + + // Build page from cached HTML + let page_response = build_cached_html_page_response(&target_url, &html); + let mut page = build(&target_url, page_response); + + if !self.configuration.external_domains_caseless.is_empty() { + page.set_external(self.configuration.external_domains_caseless.clone()); + } + page.set_url_parsed_direct(); + if return_page_links { + page.page_links = Some(Default::default()); + } + + let page_base = page.base.take().map(Box::new); + let mut links: HashSet = if full_resources { + page.links_full(&selectors, &page_base).await + } else { + page.links(&selectors, &page_base).await + }; + page.base = None; + + if normalize { + page.signature + .replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + + // Set initial metadata + self.initial_status_code = page.status_code; + self.initial_html_length = page.get_html_bytes_u8().len(); + + let url = match &self.on_link_find_callback { + Some(cb) => cb(*self.url.clone(), None).0, + _ => *self.url.clone(), + }; + self.insert_link(url).await; + self.links_visited + .insert(CaseInsensitiveString::from(target_url.as_str())); + + emit_log(&target_url); + + if normalize { + if let Some(sig) = page.signature { + if !self.is_signature_allowed(sig).await { + channel_send_page(&self.channel, page, &self.channel_guard); + self.subscription_guard().await; + return true; + } + self.insert_signature(sig).await; + } + } + + self.set_crawl_initial_status(&page, &links); + + if let Some(ref cb) = self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + self.subscription_guard().await; + return true; // blocked, but cache phase handled it + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + + // If single_page, we're done + if self.single_page() { + self.subscription_guard().await; + return true; + } + + // Follow links from cache + let mut cache_misses: HashSet = HashSet::new(); + + 'cache_loop: loop { + let current_links: Vec = links.drain().collect(); + if current_links.is_empty() { + break; + } + + for link in current_links { + let allowed = self.is_allowed(&link); + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + break 'cache_loop; + } + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + + let link_url = link.inner().to_string(); + + match get_cached_url( + &link_url, + cache_options.as_ref(), + &self.configuration.cache_policy, + ) + .await + { + Some(cached_html) => { + emit_log(&link_url); + self.insert_link(link.clone()).await; + + let page_response = + build_cached_html_page_response(&link_url, &cached_html); + let mut page = build(&link_url, page_response); + + if !self.configuration.external_domains_caseless.is_empty() { + page.set_external(self.configuration.external_domains_caseless.clone()); + } + page.set_url_parsed_direct(); + if return_page_links { + page.page_links = Some(Default::default()); + } + + let page_base = page.base.take().map(Box::new); + let new_links = if full_resources { + page.links_full(&selectors, &page_base).await + } else { + page.links(&selectors, &page_base).await + }; + page.base = None; + + if normalize { + page.signature + .replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + if let Some(sig) = page.signature { + if !self.is_signature_allowed(sig).await { + continue; + } + self.insert_signature(sig).await; + } + } + + if let Some(ref cb) = self.on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&self.channel, page, &self.channel_guard); + continue; + } + } + + channel_send_page(&self.channel, page, &self.channel_guard); + // Add newly discovered links for further cache processing + links.extend(new_links); + } + None => { + // Cache miss — save for Chrome/HTTP + cache_misses.insert(link); + } + } + } + } + + // If there are cache misses, put them in extra_links for Chrome/HTTP + if !cache_misses.is_empty() { + self.extra_links.extend(cache_misses); + return false; // Need Chrome/HTTP for remaining links + } + + self.subscription_guard().await; + true // All pages served from cache + } + + /// No-op stub when cache features not enabled. + #[cfg(not(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + )))] + async fn crawl_cache_phase(&mut self, _client: &Client) -> bool { + false + } + + /// Start to crawl website with async concurrency. + pub async fn crawl(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + self.start(); + if self.try_cache_shortcircuit().await { + self.set_crawl_status(); + return; + } + let (client, handle) = self.setup().await; + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let url = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &url, async { + self.crawl_concurrent(&client, &handle).await; + self.sitemap_crawl_chain(&client, &handle, false).await; + }) + .await; + self.set_crawl_status(); + if let Some(h) = join_handle { + h.abort() + } + self.client.replace(client); + } + } + + /// Start to crawl website with async concurrency using the sitemap. This does not page forward into the request. This does nothing without the `sitemap` flag enabled. + pub async fn crawl_sitemap(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + self.start(); + if self.try_cache_shortcircuit().await { + self.set_crawl_status(); + return; + } + let (client, handle) = self.setup().await; + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let url = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &url, async { + self.sitemap_crawl(&client, &handle, false).await; + }) + .await; + self.set_crawl_status(); + if let Some(h) = join_handle { + h.abort() + } + self.client.replace(client); + } + } + + /// Start to crawl website with async concurrency using the sitemap. This does not page forward into the request. This does nothing without the `sitemap` and the `chrome` flag enabled. + #[cfg(all( + feature = "sitemap", + feature = "chrome", + not(feature = "decentralized") + ))] + pub async fn crawl_sitemap_chrome(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + self.start(); + let (client, handle) = self.setup().await; + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let url = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &url, async { + self.sitemap_crawl_chrome(&client, &handle, false).await; + }) + .await; + self.set_crawl_status(); + if let Some(h) = join_handle { + h.abort() + } + self.client.replace(client); + } + } + + /// Configures the website crawling process for concurrent execution with the ability to send it across threads for subscriptions. + pub async fn configure_setup(&mut self) { + self.status = CrawlStatus::Active; + self.start(); + self.setup().await; + self.configuration.configure_allowlist(); + self.send_configured = true; + } + + /// Configures the website crawling process for concurrent execution with the ability to send it across threads for subscriptions without robot protection. + /// You can manually call `website.configure_robots_parser` after. + pub fn configure_setup_norobots(&mut self) { + self.status = CrawlStatus::Active; + self.start(); + self.setup_base(); + self.configuration.configure_allowlist(); + self.send_configured = true; + } + + #[cfg(not(feature = "decentralized"))] + /// Initiates the website crawling http process concurrently with the ability to send it across threads for subscriptions. + /// Ensure that `website.configure_setup()` has been called before executing this function. + /// It checks the status to ensure it is not firewall-blocked before proceeding with concurrent crawling. + /// You can pass in a manual url in order to setup a new crawl directly with pre-configurations ready. + pub async fn crawl_raw_send(&self, url: Option<&str>) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let (client, handle) = ( + match &self.client { + Some(c) => c.to_owned(), + _ => self.configure_http_client(), + }, + self.configure_handler(), + ); + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let u = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &u, async { + self.crawl_concurrent_raw_send(&client, &handle, &url).await; + }) + .await; + if let Some(h) = join_handle { + h.abort() + } + } + } + + #[cfg(all(feature = "chrome", not(feature = "decentralized")))] + /// Initiates the website crawling process concurrently with the ability to send it across threads for subscriptions. + /// Use `website.configure_setup().await` before executing this function to re-use the initial setup. + /// You can pass in a manual url in order to setup a new crawl directly with pre-configurations ready. + pub async fn crawl_chrome_send(&self, url: Option<&str>) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let (client, handle) = ( + match &self.client { + Some(c) => c.to_owned(), + _ => self.configure_http_client(), + }, + self.configure_handler(), + ); + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let u = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &u, async { + self.crawl_concurrent_send(&client, &handle, &url).await; + }) + .await; + if let Some(h) = join_handle { + h.abort() + } + } + } + + #[cfg(all(feature = "chrome", feature = "decentralized"))] + /// In decentralized builds, chrome send crawling is not supported and this is a no-op. + pub async fn crawl_chrome_send(&self, _url: Option<&str>) {} + + #[cfg(all(feature = "chrome", not(feature = "decentralized")))] + /// Initiates a single fetch with chrome for one page with the ability to send it across threads for subscriptions. + pub async fn fetch_chrome(&self, url: Option<&str>) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let (client, handle) = ( + match &self.client { + Some(c) => c.to_owned(), + _ => self.configure_http_client(), + }, + self.configure_handler(), + ); + let (_handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + self._fetch_chrome(&client, &url).await; + if let Some(h) = join_handle { + h.abort() + } + } + } + + #[cfg(all(feature = "chrome", not(feature = "decentralized")))] + /// Initiates a single fetch with chrome without closing the browser for one page with the ability to send it across threads for subscriptions. + pub async fn fetch_chrome_persisted( + &self, + url: Option<&str>, + browser: &crate::features::chrome::BrowserController, + ) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let (client, handle) = ( + match &self.client { + Some(c) => c.to_owned(), + _ => self.configure_http_client(), + }, + self.configure_handler(), + ); + let (_handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + self._fetch_chrome_persisted(&client, &url, browser).await; + if let Some(h) = join_handle { + h.abort() + } + } + } + + #[cfg(all(feature = "decentralized", feature = "smart"))] + /// Start to crawl website with async concurrency smart. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `smart` flag enabled. + pub async fn crawl_smart(&mut self) { + self.crawl().await; + } + + #[cfg(all(feature = "decentralized", not(feature = "smart")))] + /// Start to crawl website with async concurrency smart. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `smart` flag enabled. + pub async fn crawl_smart(&mut self) { + self.crawl().await; + } + + #[cfg(all(not(feature = "decentralized"), feature = "smart"))] + /// Start to crawl website with async concurrency smart. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `smart` flag enabled. + pub async fn crawl_smart(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + self.start(); + if self.try_cache_shortcircuit().await { + self.set_crawl_status(); + return; + } + let (client, handle) = self.setup().await; + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let url = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &url, async { + self.crawl_concurrent_smart(&client, &handle).await; + }) + .await; + self.set_crawl_status(); + if let Some(h) = join_handle { + h.abort() + } + self.client.replace(client); + } + } + + #[cfg(all(not(feature = "decentralized"), not(feature = "smart")))] + /// Start to crawl website with async concurrency smart. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `smart` flag enabled. + pub async fn crawl_smart(&mut self) { + self.crawl().await + } + + /// Start to crawl website with async concurrency using the base raw functionality. Useful when using the `chrome` feature and defaulting to the basic implementation. + pub async fn crawl_raw(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + self.start(); + if self.try_cache_shortcircuit().await { + self.set_crawl_status(); + return; + } + let (client, handle) = self.setup().await; + let (handle, join_handle) = match handle { + Some(h) => (Some(h.0), Some(h.1)), + _ => (None, None), + }; + let crawl_timeout = self.configuration.crawl_timeout; + let url = self.url.inner().to_string(); + run_with_crawl_timeout(crawl_timeout, &url, async { + self.crawl_concurrent_raw(&client, &handle).await; + self.sitemap_crawl_chain(&client, &handle, false).await; + }) + .await; + self.set_crawl_status(); + if let Some(h) = join_handle { + h.abort() + } + self.client.replace(client); + } + } + + /// Start to scrape/download website with async concurrency. + pub async fn scrape(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let mut w = self.clone(); + let mut rx2 = w.subscribe(0).expect("receiver enabled"); + + if self.pages.is_none() { + self.pages = Some(Vec::new()); + } + + // Signal channel to notify when crawl is done + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl().await; + w.unsubscribe(); + // Signal that crawl is complete + let _ = done_tx.send(()); + }; + + let sub = async { + loop { + tokio::select! { + biased; + // Check if crawl is done first + _ = &mut done_rx => { + break; + } + result = rx2.recv() => { + if let Ok(page) = result { + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + self.insert_link(page.get_url().into()).await; + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + // Unsubscribe from self to close the original channel for any external subscribers + self.unsubscribe(); + } + } + + /// Start to crawl website with async concurrency using the base raw functionality. Useful when using the "chrome" feature and defaulting to the basic implementation. + pub async fn scrape_raw(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let mut w = self.clone(); + let mut rx2 = w.subscribe(0).expect("receiver enabled"); + + if self.pages.is_none() { + self.pages = Some(Vec::new()); + } + + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_raw().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let sub = async { + loop { + tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx2.recv() => { + if let Ok(page) = result { + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + self.insert_link(page.get_url().into()).await; + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + self.unsubscribe(); + } + } + + /// Start to scrape website with async concurrency smart. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `smart` flag enabled. + pub async fn scrape_smart(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let mut w = self.clone(); + let mut rx2 = w.subscribe(0).expect("receiver enabled"); + + if self.pages.is_none() { + self.pages = Some(Vec::new()); + } + + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_smart().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let sub = async { + loop { + tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx2.recv() => { + if let Ok(page) = result { + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + self.insert_link(page.get_url().into()).await; + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + self.unsubscribe(); + } + } + + /// Start to scrape website sitemap with async concurrency. Use HTTP first and JavaScript Rendering as needed. This has no effect without the `sitemap` flag enabled. + pub async fn scrape_sitemap(&mut self) { + if !self.status.eq(&CrawlStatus::FirewallBlocked) { + let mut w = self.clone(); + let mut rx2 = w.subscribe(0).expect("receiver enabled"); + + if self.pages.is_none() { + self.pages = Some(Vec::new()); + } + + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_sitemap().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let sub = async { + loop { + tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx2.recv() => { + if let Ok(page) = result { + if let Some(sid) = page.signature { + self.insert_signature(sid).await; + } + self.insert_link(page.get_url().into()).await; + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + self.unsubscribe(); + } + } + + /// Dequeue the links to a set + async fn dequeue( + &mut self, + q: &mut Option>, + links: &mut HashSet, + exceeded_budget: &mut bool, + ) { + // Drain relevance credits: restore wildcard budget for irrelevant pages + #[cfg(all(feature = "agent", feature = "serde"))] + if let Some(ref cfgs) = self.configuration.remote_multimodal { + let credits = cfgs + .relevance_credits + .swap(0, std::sync::atomic::Ordering::Relaxed); + for _ in 0..credits { + self.restore_wildcard_budget(); + } + } + + if let Some(q) = q { + while let Ok(link) = q.try_recv() { + let s = link.into(); + let allowed = self.is_allowed_budgetless(&s); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + *exceeded_budget = true; + break; + } + + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&s).await { + continue; + } + + self.links_visited.extend_with_new_links(links, s); + } + } + } + + /// Apply URL pre-filter to links if configured. Returns filtered links. + #[cfg(all(feature = "agent", feature = "serde"))] + async fn apply_url_prefilter(&self, links: &mut HashSet) { + if let Some(ref cfgs) = self.configuration.remote_multimodal { + if cfgs.cfg.url_prefilter && cfgs.cfg.relevance_gate && !links.is_empty() { + *links = crate::features::automation::prefilter_urls(cfgs, links).await; + } + } + } + + /// Start to crawl website concurrently - used mainly for chrome instances to connect to default raw HTTP. + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn crawl_concurrent_raw(&mut self, client: &Client, handle: &Option>) { + self.start(); + + // Cache-only phase first + if self.crawl_cache_phase(client).await { + return; // All pages served from cache + } + if !self.extra_links.is_empty() { + self.skip_initial = true; + } + + self.status = CrawlStatus::Active; + let client_rotator = self.client_rotator.clone(); + #[cfg(feature = "hedge")] + let hedge_config = self.configuration.hedge.clone(); + let mut selector: ( + CompactString, + smallvec::SmallVec<[CompactString; 2]>, + CompactString, + ) = self.setup_selectors(); + if self.single_page() { + self._crawl_establish(client, &mut selector, false).await; + } else { + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let only_html = self.configuration.only_html && !full_resources; + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + let cache_options_raw = self.configuration.get_cache_options(); + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + let cache_policy_raw = self.configuration.cache_policy.clone(); + #[cfg(any( + feature = "cache", + feature = "cache_mem", + feature = "chrome_remote_cache" + ))] + let normalize_raw = self.configuration.normalize; + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let (mut interval, throttle) = self.setup_crawl(); + + let mut links: HashSet = self.drain_extra_links().collect(); + + links.extend(self._crawl_establish(client, &mut selector, false).await); + + self.configuration.configure_allowlist(); + + let semaphore = self.setup_semaphore(); + + let shared = Arc::new(( + client.to_owned(), + selector, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + self.configuration.retry, + self.configuration.full_resources, + PageLinkBuildSettings::new_full( + false, + self.configuration.full_resources, + self.configuration.subdomains, + self.configuration.tld, + self.configuration.normalize, + ), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + self.configuration.remote_multimodal.clone(), + )); + + let mut set: JoinSet<(HashSet, Option)> = JoinSet::new(); + + // track budgeting one time. + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter(links.drain().collect::>()); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue).await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self.handle_process(handle, &mut interval, async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }).await { + while let Some(links) = stream.next().await { + self.extra_links.insert(links); + } + break 'outer; + } + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + + self.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + let rotator = client_rotator.clone(); + #[cfg(feature = "hedge")] + let hedge_cfg = hedge_config.clone(); + #[cfg(any(feature = "cache", feature = "cache_mem", feature = "chrome_remote_cache"))] + let cache_opts = cache_options_raw.clone(); + #[cfg(any(feature = "cache", feature = "cache_mem", feature = "chrome_remote_cache"))] + let cache_pol = cache_policy_raw.clone(); + #[cfg(any(feature = "cache", feature = "cache_mem", feature = "chrome_remote_cache"))] + let normalize = normalize_raw; + spawn_set("page_fetch", &mut set, async move { + let link_result = match &shared.9 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let target_url = link_result.0.as_ref(); + + // Cache-first: skip HTTP fetch entirely for cached pages + #[cfg(any(feature = "cache", feature = "cache_mem", feature = "chrome_remote_cache"))] + { + use crate::utils::{cache_skip_browser, get_cached_url, build_cached_html_page_response}; + if cache_skip_browser(&cache_opts) { + if let Some(html) = get_cached_url(target_url, cache_opts.as_ref(), &cache_pol).await { + let page_response = build_cached_html_page_response(target_url, &html); + let mut page = build(target_url, page_response); + + if !shared.3.is_empty() { + page.set_external(shared.3.clone()); + } + page.set_url_parsed_direct(); + if return_page_links { + page.page_links = Some(Default::default()); + } + let page_base = page.base.take().map(Box::new); + let links = if full_resources { + page.links_full(&shared.1, &page_base).await + } else { + page.links(&shared.1, &page_base).await + }; + page.base = None; + if normalize { + page.signature.replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default(); + } + } + let signature = page.signature; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return (links, signature); + } + } + } + + let external_domains_caseless = &shared.3; + + // Hedge-enabled path: race primary vs delayed hedge on different proxy + #[cfg(feature = "hedge")] + let (mut page, mut links, mut links_pages) = { + let should_hedge = if let Some(ref hcfg) = hedge_cfg { + hcfg.enabled && rotator.as_ref().map_or(false, |r| r.len() > 1) + } else { + false + }; + + if should_hedge { + let hcfg = hedge_cfg.as_ref().unwrap(); + let rot = rotator.as_ref().unwrap(); + let (primary_client, hedge_client_opt) = rot.next_pair(); + + if let Some(hedge_client) = hedge_client_opt { + let delay = hcfg.delay; + + let primary_fut = async { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, primary_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + }; + + tokio::pin!(primary_fut); + + tokio::select! { + biased; + result = &mut primary_fut => result, + _ = tokio::time::sleep(delay) => { + log::info!("[hedge] fired after {}ms url={}", delay.as_millis(), target_url); + + let hedge_fut = async { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, hedge_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + }; + + tokio::pin!(hedge_fut); + + tokio::select! { + biased; + result = &mut primary_fut => { + log::info!("[hedge] winner: primary url={}", target_url); + result + } + result = &mut hedge_fut => { + log::info!("[hedge] winner: hedge url={}", target_url); + result + } + } + } + } + } else { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, primary_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + } + } else { + let client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + } + }; + + #[cfg(not(feature = "hedge"))] + let (mut page, mut links, mut links_pages) = { + let client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { + Some(links.clone()) + } else { + None + }; + let mut relative_selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, + client, only_html, + &mut relative_selectors, + external_domains_caseless, + &r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await; + (page, links, links_pages) + }; + + let mut retry_count = shared.5; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + + let retry_client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let mut domain_parsed = None; + let mut retry_r_settings = shared.7; + retry_r_settings.ssg_build = true; + let next_page = Page::new_page_streaming( + target_url, + retry_client, only_html, + &mut shared.1.clone(), + external_domains_caseless, + &retry_r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await; + + page.clone_from(&next_page); + + }).await + { + log::warn!("Handler timeout exceeded {elasped}"); + } + + } else { + let mut domain_parsed = None; + let mut retry_r_settings = shared.7; + retry_r_settings.ssg_build = true; + page.clone_from(&Page::new_page_streaming( + target_url, + retry_client, + only_html, + &mut shared.1.clone(), + external_domains_caseless, + &retry_r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await); + } + } + + if return_page_links { + page.page_links = links_pages.filter(|pages| !pages.is_empty()).map(Box::new); + } + + // Run remote multimodal extraction if configured (HTTP-only path) + #[cfg(all(feature = "agent", feature = "serde"))] + if shared.10.is_some() { + let html = page.get_html(); + if !html.is_empty() { + use crate::features::automation::{run_remote_multimodal_extraction, AutomationResultExt}; + let title = page.metadata.as_ref().and_then(|m| m.title.as_ref()).map(|t| t.as_str()); + if let Ok(Some(result)) = run_remote_multimodal_extraction( + &shared.10, + &html, + target_url, + title, + ).await { + // Store usage on page + match page.remote_multimodal_usage.as_mut() { + Some(v) => v.push(result.usage.clone()), + None => page.remote_multimodal_usage = Some(vec![result.usage.clone()]), + } + // Store extracted data if available + if result.extracted.is_some() || result.screenshot.is_some() { + let automation_result = result.to_automation_results(); + match page.extra_remote_multimodal_data.as_mut() { + Some(v) => v.push(automation_result), + None => page.extra_remote_multimodal_data = Some(vec![automation_result]), + } + } + } + } + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default() + } + } + + let signature = page.signature; + + channel_send_page(&shared.2, page, &shared.4); + + drop(permit); + + (links, signature) + }); + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + }, + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if self.is_signature_allowed(signature).await { + self.insert_signature(signature).await; + self.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + self.links_visited.extend_links(&mut links, res.0); + } + } + } else { + break; + } + } + else => break, + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() || exceeded_budget { + // await for all tasks to complete. + if exceeded_budget { + while let Some(links) = stream.next().await { + self.extra_links.insert(links); + } + while let Some(links) = set.join_next().await { + if let Ok(links) = links { + self.extra_links.extend(links.0); + } + } + } + break 'outer; + } + } + + self.subscription_guard().await; + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + // store the extra links. + if !links.is_empty() { + self.extra_links.extend(links); + } + } + } + + /// Start to crawl website concurrently. + #[cfg(all(not(feature = "decentralized"), feature = "chrome"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn crawl_concurrent(&mut self, client: &Client, handle: &Option>) { + use crate::features::chrome::attempt_navigation; + self.start(); + + // Phase 1: Try cache-only crawl (no Chrome, no HTTP) + if self.crawl_cache_phase(client).await { + return; // All pages served from cache — skip Chrome entirely + } + // If cache_phase returned false with cache misses, they're in self.extra_links. + if !self.extra_links.is_empty() { + self.skip_initial = true; + } + + // Phase 2: Chrome for remaining pages + match self.setup_browser().await { + Some(mut b) => { + match attempt_navigation( + "about:blank", + &b.browser.0, + &self.configuration.request_timeout, + &b.browser.2, + &self.configuration.viewport, + ) + .await + { + Ok(new_page) => { + let mut selectors = self.setup_selectors(); + self.status = CrawlStatus::Active; + + if self.single_page() { + self.crawl_establish(client, &mut selectors, false, &new_page) + .await; + drop(new_page); + self.subscription_guard().await; + b.dispose(); + } else { + let semaphore: Arc = self.setup_semaphore(); + let (mut interval, throttle) = self.setup_crawl(); + + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let base_links = self + .crawl_establish(client, &mut selectors, false, &new_page) + .await; + + drop(new_page); + + let mut links: HashSet = + self.drain_extra_links().collect(); + + links.extend(base_links); + + self.configuration.configure_allowlist(); + + let mut set: JoinSet<(HashSet, Option)> = + JoinSet::new(); + + let shared = Arc::new(( + client.to_owned(), + selectors, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + b.browser.0.clone(), + self.configuration.clone(), + self.url.inner().to_string(), + b.browser.2.clone(), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + )); + + let add_external = !shared.3.is_empty(); + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter::>( + links.drain().collect(), + ); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue) + .await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self + .handle_process( + handle, + &mut interval, + async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }, + ) + .await + { + break 'outer; + } + + let allowed = self.is_allowed(&link); + + if allowed + .eq(&ProcessLinkStatus::BudgetExceeded) + { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + + self.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + spawn_set("page_fetch", &mut set, async move { + let link_result = + match &shared.10 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let target_url_string = link_result.0.as_ref().to_string(); + + // Cache-first: skip tab creation entirely for cached pages + #[cfg(any(feature = "cache", feature = "cache_mem", feature = "chrome_remote_cache"))] + { + use crate::utils::{cache_skip_browser, get_cached_url, build_cached_html_page_response}; + let cache_options = shared.6.get_cache_options(); + if cache_skip_browser(&cache_options) { + if let Some(html) = get_cached_url(&target_url_string, cache_options.as_ref(), &shared.6.cache_policy).await { + let page_response = build_cached_html_page_response(&target_url_string, &html); + let mut page = build(&target_url_string, page_response); + + if add_external { + page.set_external(shared.3.clone()); + } + page.set_url_parsed_direct(); + let page_base = page.base.take().map(Box::new); + if return_page_links { + page.page_links = Some(Default::default()); + } + let links = if full_resources { + page.links_full(&shared.1, &page_base).await + } else { + page.links(&shared.1, &page_base).await + }; + page.base = None; + if shared.6.normalize { + page.signature.replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default(); + } + } + let signature = page.signature; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return (links, signature); + } + } + } + + let results = match attempt_navigation("about:blank", &shared.5, &shared.6.request_timeout, &shared.8, &shared.6.viewport).await { + Ok(new_page) => { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events(&new_page, &shared.6), + crate::features::chrome::setup_chrome_interception_base( + &new_page, + shared.6.chrome_intercept.enabled, + &shared.6.auth_challenge_response, + shared.6.chrome_intercept.block_visuals, + &shared.7, + ) + ); + + let target_url = target_url_string.as_str(); + + let mut page = Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ) + .await; + + let mut retry_count = shared.6.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let p = Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ).await; + page.clone_from(&p); + + }).await { + log::info!("{target_url} backoff gateway timeout exceeded {elasped}"); + } + } else { + page.clone_from( + &Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ) + .await, + ); + } + } + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if add_external { + page.set_external(shared.3.clone()); + } + + let prev_domain = page.base.take(); + + // Use page's own URL for relative link resolution (not original crawl domain). + // Fixes subdomain pages resolving e.g. href="/path" against wrong host. + page.set_url_parsed_direct(); + let page_base = page.base.take().map(Box::new); + + if return_page_links { + page.page_links = Some(Default::default()); + } + + let links = if full_resources { + page.links_full(&shared.1, &page_base).await + } else { + page.links(&shared.1, &page_base).await + }; + + page.base = prev_domain; + + if shared.6.normalize { + page.signature.replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default() + } + } + + let signature = page.signature; + + channel_send_page( + &shared.2, page, &shared.4, + ); + + (links, signature) + } + _ => Default::default(), + }; + + + drop(permit); + + results + }); + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + } + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if self.is_signature_allowed(signature).await { + self.insert_signature(signature).await; + self.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + self.links_visited.extend_links(&mut links, res.0); + } + } + } else{ + break + } + } + else => break, + }; + + if links.is_empty() && set.is_empty() || exceeded_budget { + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + self.subscription_guard().await; + b.dispose(); + // store the extra links. + if !links.is_empty() { + self.extra_links.extend(links); + } + } + } + Err(err) => { + b.dispose(); + log::error!("{}", err) + } + } + } + _ => log::error!("Chrome initialization failed."), + } + } + + /// Start to crawl website concurrently using chrome with the ability to send it across threads for subscriptions. + #[cfg(not(feature = "decentralized"))] + #[cfg_attr( + all(feature = "tracing", not(feature = "decentralized")), + tracing::instrument(skip_all) + )] + async fn crawl_concurrent_raw_send( + &self, + client: &Client, + handle: &Option>, + url: &Option<&str>, + ) -> Website { + let mut selector: ( + CompactString, + smallvec::SmallVec<[CompactString; 2]>, + CompactString, + ) = self.setup_selectors(); + + let mut website = self.clone(); + + if let Some(u) = url { + match &website.domain_parsed { + Some(domain_url) => { + if domain_url.as_str().starts_with(u) { + website.set_url_only(u); + } else { + website.set_url(u); + } + } + _ => { + website.set_url(u); + } + } + } + + if !website.send_configured { + website.configure_setup().await; + } + + if self.single_page() { + website._crawl_establish(client, &mut selector, false).await; + website + } else { + let client_rotator = self.client_rotator.clone(); + #[cfg(feature = "hedge")] + let hedge_config = self.configuration.hedge.clone(); + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let only_html = self.configuration.only_html && !full_resources; + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let (mut interval, throttle) = self.setup_crawl(); + + let mut links: HashSet = website.drain_extra_links().collect(); + + links.extend(website._crawl_establish(client, &mut selector, false).await); + + let semaphore = self.setup_semaphore(); + + let shared = Arc::new(( + client.to_owned(), + selector, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + self.configuration.retry, + self.configuration.full_resources, + PageLinkBuildSettings::new_full( + false, + self.configuration.full_resources, + self.configuration.subdomains, + self.configuration.tld, + self.configuration.normalize, + ), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + self.configuration.remote_multimodal.clone(), + )); + + let mut set: JoinSet<(HashSet, Option)> = JoinSet::new(); + + // track budgeting one time. + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + website + .dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter(links.drain().collect::>()); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue).await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self.handle_process(handle, &mut interval, async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }).await { + break 'outer; + } + let allowed = website.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + + website.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + let rotator = client_rotator.clone(); + #[cfg(feature = "hedge")] + let hedge_cfg = hedge_config.clone(); + spawn_set("page_fetch", &mut set, async move { + let link_result = match &shared.9 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let target_url = link_result.0.as_ref(); + let external_domains_caseless = &shared.3; + + // Hedge-enabled path + #[cfg(feature = "hedge")] + let (mut page, mut links, mut links_pages) = { + let should_hedge = if let Some(ref hcfg) = hedge_cfg { + hcfg.enabled && rotator.as_ref().map_or(false, |r| r.len() > 1) + } else { + false + }; + + if should_hedge { + let hcfg = hedge_cfg.as_ref().unwrap(); + let rot = rotator.as_ref().unwrap(); + let (primary_client, hedge_client_opt) = rot.next_pair(); + + if let Some(hedge_client) = hedge_client_opt { + let delay = hcfg.delay; + + let primary_fut = async { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, primary_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + }; + + tokio::pin!(primary_fut); + + tokio::select! { + biased; + result = &mut primary_fut => result, + _ = tokio::time::sleep(delay) => { + log::info!("[hedge] fired after {}ms url={}", delay.as_millis(), target_url); + + let hedge_fut = async { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, hedge_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + }; + + tokio::pin!(hedge_fut); + + tokio::select! { + biased; + result = &mut primary_fut => { + log::info!("[hedge] winner: primary url={}", target_url); + result + } + result = &mut hedge_fut => { + log::info!("[hedge] winner: hedge url={}", target_url); + result + } + } + } + } + } else { + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, primary_client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + } + } else { + let client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { Some(HashSet::new()) } else { None }; + let mut selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, client, only_html, + &mut selectors, external_domains_caseless, + &r_settings, &mut links, None, &shared.8, + &mut domain_parsed, &mut links_pages).await; + (page, links, links_pages) + } + }; + + #[cfg(not(feature = "hedge"))] + let (mut page, mut links, mut links_pages) = { + let client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + let mut links: HashSet = HashSet::new(); + let mut links_pages = if return_page_links { + Some(links.clone()) + } else { + None + }; + let mut relative_selectors = shared.1.clone(); + let mut r_settings = shared.7; + r_settings.ssg_build = true; + let mut domain_parsed = None; + let page = Page::new_page_streaming( + target_url, + client, only_html, + &mut relative_selectors, + external_domains_caseless, + &r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await; + (page, links, links_pages) + }; + + let mut retry_count = shared.5; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + + let retry_client = match &rotator { + Some(r) => r.next(), + None => &shared.0, + }; + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let mut domain_parsed = None; + let mut retry_r_settings = shared.7; + retry_r_settings.ssg_build = true; + let next_page = Page::new_page_streaming( + target_url, + retry_client, only_html, + &mut shared.1.clone(), + external_domains_caseless, + &retry_r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await; + + page.clone_from(&next_page); + + }).await + { + log::warn!("Handler timeout exceeded {elasped}"); + } + + } else { + let mut domain_parsed = None; + let mut retry_r_settings = shared.7; + retry_r_settings.ssg_build = true; + page.clone_from(&Page::new_page_streaming( + target_url, + retry_client, + only_html, + &mut shared.1.clone(), + external_domains_caseless, + &retry_r_settings, + &mut links, + None, + &shared.8, + &mut domain_parsed, + &mut links_pages).await); + } + } + + if return_page_links { + page.page_links = links_pages.filter(|pages| !pages.is_empty()).map(Box::new); + } + + // Run remote multimodal extraction if configured (HTTP-only path) + #[cfg(all(feature = "agent", feature = "serde"))] + if shared.10.is_some() { + let html = page.get_html(); + if !html.is_empty() { + use crate::features::automation::{run_remote_multimodal_extraction, AutomationResultExt}; + let title = page.metadata.as_ref().and_then(|m| m.title.as_ref()).map(|t| t.as_str()); + if let Ok(Some(result)) = run_remote_multimodal_extraction( + &shared.10, + &html, + target_url, + title, + ).await { + // Store usage on page + match page.remote_multimodal_usage.as_mut() { + Some(v) => v.push(result.usage.clone()), + None => page.remote_multimodal_usage = Some(vec![result.usage.clone()]), + } + // Store extracted data if available + if result.extracted.is_some() || result.screenshot.is_some() { + let automation_result = result.to_automation_results(); + match page.extra_remote_multimodal_data.as_mut() { + Some(v) => v.push(automation_result), + None => page.extra_remote_multimodal_data = Some(vec![automation_result]), + } + } + } + } + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default() + } + } + + let signature = page.signature; + + channel_send_page(&shared.2, page, &shared.4); + + drop(permit); + + (links, signature) + }); + } + + website.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + }, + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if website.is_signature_allowed(signature).await { + website.insert_signature(signature).await; + website.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + website.links_visited.extend_links(&mut links, res.0); + } + } + } else { + break; + } + } + else => break, + } + + website + .dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + + if links.is_empty() && set.is_empty() || exceeded_budget { + // await for all tasks to complete. + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + + website.subscription_guard().await; + website + .dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + + if links.is_empty() && set.is_empty() { + break; + } + } + website + } + } + + /// Start to crawl website concurrently with the ability to send it across threads for subscriptions. + #[cfg(all(not(feature = "decentralized"), feature = "chrome"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn crawl_concurrent_send( + &self, + client: &Client, + handle: &Option>, + url: &Option<&str>, + ) -> Website { + use crate::features::chrome::attempt_navigation; + + match self.setup_browser().await { + Some(mut b) => { + match attempt_navigation( + "about:blank", + &b.browser.0, + &self.configuration.request_timeout, + &b.browser.2, + &self.configuration.viewport, + ) + .await + { + Ok(new_page) => { + let mut selectors = self.setup_selectors(); + let mut website = self.to_owned(); + + if let Some(u) = url { + match &website.domain_parsed { + Some(domain_url) => { + if domain_url.as_str().starts_with(u) { + website.set_url_only(u); + } else { + website.set_url(u); + } + } + _ => { + website.set_url(u); + } + } + } + + if !website.send_configured { + website.configure_setup().await; + } + + let base_links = website + .crawl_establish(client, &mut selectors, false, &new_page) + .await; + + drop(new_page); + + if self.single_page() { + website.subscription_guard().await; + b.dispose(); + website + } else { + let semaphore: Arc = self.setup_semaphore(); + let (mut interval, throttle) = self.setup_crawl(); + + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let mut links: HashSet = + *self.extra_links.clone(); + + links.extend(base_links); + + let mut set: JoinSet<(HashSet, Option)> = + JoinSet::new(); + + let shared = Arc::new(( + client.to_owned(), + selectors, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + b.browser.0.clone(), + self.configuration.clone(), + self.url.inner().to_string(), + b.browser.2.clone(), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + )); + + let add_external = !shared.3.is_empty(); + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + website + .dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter::>( + links.drain().collect(), + ); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue) + .await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self + .handle_process( + handle, + &mut interval, + async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }, + ) + .await + { + break 'outer; + } + + let allowed = website.is_allowed(&link); + + if allowed + .eq(&ProcessLinkStatus::BudgetExceeded) + { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + + website.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + spawn_set("page_fetch", &mut set, async move { + let results = match attempt_navigation("about:blank", &shared.5, &shared.6.request_timeout, &shared.8, &shared.6.viewport).await { + Ok(new_page) => { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events(&new_page, &shared.6), + crate::features::chrome::setup_chrome_interception_base( + &new_page, + shared.6.chrome_intercept.enabled, + &shared.6.auth_challenge_response, + shared.6.chrome_intercept.block_visuals, + &shared.7, + ) + ); + + let link_result = + match &shared.10 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let target_url = link_result.0.as_ref(); + + let mut page = Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ) + .await; + + let mut retry_count = shared.6.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let p = Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ).await; + page.clone_from(&p); + + }).await { + log::info!("{target_url} backoff gateway timeout exceeded {elasped}"); + } + } else { + page.clone_from( + &Page::new( + target_url, + &shared.0, + &new_page, + &shared.6.wait_for, + &shared.6.screenshot, + false, + &shared.6.openai_config, + &shared.6.execution_scripts, + &shared.6.automation_scripts, + &shared.6.viewport, + &shared.6.request_timeout, + &shared.6.track_events, + shared.6.referer.clone(), + shared.6.max_page_bytes, + shared.6.get_cache_options(), + &shared.6.cache_policy, + &shared.6.remote_multimodal, + ) + .await, + ); + } + } + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if add_external { + page.set_external(shared.3.clone()); + } + + let prev_domain = page.base.take(); + + // Use page's own URL for relative link resolution (not original crawl domain). + // Fixes subdomain pages resolving e.g. href="/path" against wrong host. + page.set_url_parsed_direct(); + let page_base = page.base.take().map(Box::new); + + if return_page_links { + page.page_links = Some(Default::default()); + } + + let links = if full_resources { + page.links_full(&shared.1, &page_base).await + } else { + page.links(&shared.1, &page_base).await + }; + + page.base = prev_domain; + + if shared.6.normalize { + page.signature.replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default() + } + } + + let signature = page.signature; + + channel_send_page( + &shared.2, page, &shared.4, + ); + + (links, signature) + } + _ => Default::default(), + }; + + + drop(permit); + + results + }); + } + + website.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + } + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if website.is_signature_allowed(signature).await { + website.insert_signature(signature).await; + website.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + website.links_visited.extend_links(&mut links, res.0); + } + } + } else{ + break + } + } + else => break, + }; + + if links.is_empty() && set.is_empty() || exceeded_budget { + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + + website + .dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + website.subscription_guard().await; + b.dispose(); + + website + } + } + Err(err) => { + b.dispose(); + log::error!("{}", err); + self.clone() + } + } + } + _ => { + log::error!("Chrome initialization failed."); + self.clone() + } + } + } + + /// Start to crawl website concurrently with the ability to send it across threads for subscriptions for one page. + #[cfg(all(not(feature = "decentralized"), feature = "chrome"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn _fetch_chrome(&self, client: &Client, url: &Option<&str>) { + use crate::features::chrome::attempt_navigation; + + match self.setup_browser().await { + Some(mut b) => { + match attempt_navigation( + "about:blank", + &b.browser.0, + &self.configuration.request_timeout, + &b.browser.2, + &self.configuration.viewport, + ) + .await + { + Ok(new_page) => { + let mut selectors = self.setup_selectors(); + self.crawl_establish_chrome_one(client, &mut selectors, url, &new_page) + .await; + self.subscription_guard().await; + b.dispose(); + } + Err(err) => { + b.dispose(); + log::error!("{}", err); + } + } + } + _ => { + log::error!("Chrome initialization failed."); + } + } + } + + /// Start to crawl website concurrently with the ability to send it across threads for subscriptions for one page. + #[cfg(all(not(feature = "decentralized"), feature = "chrome"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn _fetch_chrome_persisted( + &self, + client: &Client, + url: &Option<&str>, + b: &crate::features::chrome::BrowserController, + ) { + use crate::features::chrome::attempt_navigation; + match attempt_navigation( + "about:blank", + &b.browser.0, + &self.configuration.request_timeout, + &b.browser.2, + &self.configuration.viewport, + ) + .await + { + Ok(new_page) => { + let mut selectors = self.setup_selectors(); + self.crawl_establish_chrome_one(client, &mut selectors, url, &new_page) + .await; + self.subscription_guard().await; + } + Err(err) => { + log::error!("{}", err); + } + } + } + + /// Start to crawl website concurrently using WebDriver. + #[cfg(all( + not(feature = "decentralized"), + not(feature = "chrome"), + feature = "webdriver" + ))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn crawl_concurrent_webdriver( + &mut self, + client: &Client, + handle: &Option>, + ) { + self.start(); + + match self.setup_webdriver().await { + Some(mut controller) => { + let driver = controller.driver(); + let mut selectors = self.setup_selectors(); + self.status = CrawlStatus::Active; + + if self.single_page() { + self.crawl_establish_webdriver_one(client, &mut selectors, &None, driver) + .await; + self.subscription_guard().await; + controller.dispose(); + } else { + let semaphore: Arc = self.setup_semaphore(); + let (mut interval, throttle) = self.setup_crawl(); + + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let base_links = self + .crawl_establish_webdriver_one(client, &mut selectors, &None, driver) + .await; + + let mut links: HashSet = + self.drain_extra_links().collect(); + + links.extend(base_links); + + self.configuration.configure_allowlist(); + + let timeout = self + .configuration + .webdriver_config + .as_ref() + .and_then(|c| c.timeout); + + let mut set: JoinSet<(HashSet, Option)> = + JoinSet::new(); + + let shared = Arc::new(( + client.to_owned(), + selectors, + self.channel.clone(), + self.configuration.external_domains_caseless.clone(), + self.channel_guard.clone(), + driver.clone(), + self.configuration.clone(), + self.url.inner().to_string(), + self.domain_parsed.clone(), + self.on_link_find_callback.clone(), + timeout, + )); + + let add_external = !shared.3.is_empty(); + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let full_resources = self.configuration.full_resources; + let return_page_links = self.configuration.return_page_links; + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter::>( + links.drain().collect(), + ); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue).await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self + .handle_process( + handle, + &mut interval, + async { + emit_log_shutdown(link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + }, + ) + .await + { + break 'outer; + } + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(link.inner()); + + self.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + + spawn_set("page_fetch_webdriver", &mut set, async move { + let link_result = match &shared.9 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let target_url = link_result.0.as_ref(); + + // Setup stealth events before navigation + crate::features::webdriver::setup_driver_events(&shared.5, &shared.6).await; + + let mut page = Page::new_page_webdriver( + target_url, + &shared.5, + shared.10, + ) + .await; + + let mut retry_count = shared.6.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + if let Some(timeout_duration) = page.get_timeout() { + tokio::time::sleep(timeout_duration).await; + } + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + if let Err(elapsed) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + let p = Page::new_page_webdriver( + target_url, + &shared.5, + shared.10, + ).await; + page.clone_from(&p); + }).await { + log::info!("{target_url} backoff gateway timeout exceeded {elapsed}"); + } + } else { + page.clone_from( + &Page::new_page_webdriver( + target_url, + &shared.5, + shared.10, + ) + .await, + ); + } + } + + if add_external { + page.set_external(shared.3.clone()); + } + + let prev_domain = page.base.take(); + + // Use page's own URL for relative link resolution (not original crawl domain). + // Fixes subdomain pages resolving e.g. href="/path" against wrong host. + page.set_url_parsed_direct(); + let page_base = page.base.take().map(Box::new); + + if return_page_links { + page.page_links = Some(Default::default()); + } + + let links = if full_resources { + page.links_full(&shared.1, &page_base).await + } else { + page.links(&shared.1, &page_base).await + }; + + page.base = prev_domain; + + if shared.6.normalize { + page.signature.replace(crate::utils::hash_html(page.get_html_bytes_u8()).await); + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.4); + drop(permit); + return Default::default(); + } + } + + let signature = page.signature; + + channel_send_page(&shared.2, page, &shared.4); + + drop(permit); + + (links, signature) + }); + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + } + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if self.is_signature_allowed(signature).await { + self.insert_signature(signature).await; + self.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + self.links_visited.extend_links(&mut links, res.0); + } + } + } else { + break + } + + if links.is_empty() && set.is_empty() || exceeded_budget { + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + else => break, + }; + + if links.is_empty() && set.is_empty() || exceeded_budget { + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + self.subscription_guard().await; + controller.dispose(); + + if !links.is_empty() { + self.extra_links.extend(links); + } + } + } + None => { + log::error!("WebDriver initialization failed."); + } + } + } + + /// Start to crawl website concurrently. + #[cfg(all( + not(feature = "decentralized"), + not(feature = "chrome"), + feature = "webdriver" + ))] + pub async fn crawl_concurrent(&mut self, client: &Client, handle: &Option>) { + // Use WebDriver if configured, otherwise fall back to raw HTTP + if self.configuration.webdriver_config.is_some() { + self.crawl_concurrent_webdriver(client, handle).await + } else { + self.crawl_concurrent_raw(client, handle).await + } + } + + /// Start to crawl website concurrently. + #[cfg(all( + not(feature = "decentralized"), + not(feature = "chrome"), + not(feature = "webdriver") + ))] + pub async fn crawl_concurrent(&mut self, client: &Client, handle: &Option>) { + self.crawl_concurrent_raw(client, handle).await + } + + /// Start to crawl website concurrently. + #[cfg(feature = "decentralized")] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub async fn crawl_concurrent(&mut self, client: &Client, handle: &Option>) { + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + self.configuration.configure_allowlist(); + let domain = self.url.inner().as_str(); + let mut interval = Box::pin(tokio::time::interval(Duration::from_millis(10))); + let throttle = Box::pin(self.get_delay()); + let on_link_find_callback = self.on_link_find_callback.clone(); + // http worker verify + let http_worker = std::env::var("SPIDER_WORKER") + .unwrap_or_else(|_| "http:".to_string()) + .starts_with("http:"); + + let mut links: HashSet = self + .crawl_establish(client, &(domain.into(), Default::default()), http_worker) + .await; + + let mut set: JoinSet> = JoinSet::new(); + let mut exceeded_budget = false; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let stream = tokio_stream::iter(links.drain().collect::>()).throttle(*throttle); + tokio::pin!(stream); + + loop { + match stream.next().await { + Some(link) => { + if !self + .handle_process(handle, &mut interval, async { + emit_log_shutdown(link.inner()); + set.shutdown().await; + }) + .await + { + break 'outer; + } + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) + || !self.is_allowed_disk(&link).await + { + continue; + } + + emit_log(link.inner()); + + self.insert_link(link.clone()).await; + + if let Ok(permit) = SEM.acquire().await { + let client = client.clone(); + let on_link_find_callback = on_link_find_callback.clone(); + + spawn_set("page_fetch", &mut set, async move { + let link_results = match &on_link_find_callback.clone() { + Some(cb) => cb(link, None), + _ => (link, None), + }; + let link_results = link_results.0.as_ref(); + let page = Page::new_links_only( + &if http_worker && link_results.starts_with("https") { + link_results.replacen("https", "http", 1).to_string() + } else { + link_results.to_string() + }, + &client, + ) + .await; + + drop(permit); + + page.links + }); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + } + } + _ => break, + } + if exceeded_budget { + break; + } + } + + while let Some(res) = set.join_next().await { + if let Ok(msg) = res { + self.links_visited.extend_links(&mut links, msg); + } + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() || exceeded_budget { + break; + } + } + + if !links.is_empty() { + self.extra_links.extend(links); + } + } + + #[cfg(all(feature = "chrome", feature = "real_browser"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + /// Warm up the gemini model. + pub async fn warm_up_gemini(&mut self) { + use crate::features::chrome::attempt_navigation; + + if let Some(mut b) = self.setup_browser().await { + if let Ok(page) = attempt_navigation( + "about:blank", + &b.browser.0, + &self.configuration.request_timeout, + &b.browser.2, + &self.configuration.viewport, + ) + .await + { + let _ = crate::features::solvers::warm_gemini_model(&page).await; + b.dispose(); + } + } + } + + /// Start to crawl website concurrently using HTTP by default and chrome Javascript Rendering as needed. The glob feature does not work with this at the moment. + #[cfg(all(not(feature = "decentralized"), feature = "smart"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub async fn crawl_concurrent_smart( + &mut self, + client: &Client, + handle: &Option>, + ) { + use tokio::sync::OnceCell; + self.start(); + self.status = CrawlStatus::Active; + let browser: crate::features::chrome::OnceBrowser = OnceCell::new(); + + let mut selectors: ( + CompactString, + smallvec::SmallVec<[CompactString; 2]>, + CompactString, + ) = self.setup_selectors(); + + if self.single_page() { + self.subscription_guard().await; + self.crawl_establish_smart(&client, &mut selectors, &browser) + .await; + } else { + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + + let mut links: HashSet = self.drain_extra_links().collect(); + + let (mut interval, throttle) = self.setup_crawl(); + let on_should_crawl_callback = self.on_should_crawl_callback.clone(); + let return_page_links = self.configuration.return_page_links; + + links.extend( + self.crawl_establish_smart(&client, &mut selectors, &browser) + .await, + ); + + self.configuration.configure_allowlist(); + + let mut set: JoinSet<(HashSet, Option)> = JoinSet::new(); + let semaphore = self.setup_semaphore(); + + let shared = Arc::new(( + client.to_owned(), + selectors, + self.channel.clone(), + self.channel_guard.clone(), + self.configuration.clone(), + self.domain_parsed.clone(), + browser, + self.on_link_find_callback.clone(), + self.cookie_jar.clone(), + )); + + let add_external = self.configuration.external_domains_caseless.len() > 0; + let mut exceeded_budget = false; + let concurrency = throttle.is_zero(); + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if !concurrency && !links.is_empty() { + tokio::time::sleep(*throttle).await; + } + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + 'outer: loop { + #[cfg(all(feature = "agent", feature = "serde"))] + self.apply_url_prefilter(&mut links).await; + + let mut stream = tokio_stream::iter(links.drain().collect::>()); + + loop { + if !concurrency { + tokio::time::sleep(*throttle).await; + } + + let semaphore = + get_semaphore(&semaphore, !self.configuration.shared_queue).await; + + tokio::select! { + biased; + Some(link) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self + .handle_process( + handle, + &mut interval, + async { + emit_log_shutdown(&link.inner()); + let permits = set.len(); + set.shutdown().await; + semaphore.add_permits(permits); + + }, + ) + .await + { + break 'outer; + } + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) || !self.is_allowed_disk(&link).await { + continue; + } + + emit_log(&link.inner()); + self.insert_link(link.clone()).await; + + if let Ok(permit) = semaphore.clone().acquire_owned().await { + let shared = shared.clone(); + let on_should_crawl_callback = on_should_crawl_callback.clone(); + spawn_set("page_fetch", &mut set, async move { + let link_result = match &shared.7 { + Some(cb) => cb(link, None), + _ => (link, None), + }; + + let url = link_result.0.as_ref(); + let mut page = Page::new_page_with_cache( + &url, + &shared.0, + shared.4.get_cache_options(), + &shared.4.cache_policy, + ) + .await; + + let mut retry_count = shared.4.retry; + + while page.should_retry && retry_count > 0 { + retry_count -= 1; + + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + + if page.status_code == StatusCode::GATEWAY_TIMEOUT { + + if let Err(elasped) = tokio::time::timeout(BACKOFF_MAX_DURATION, async { + if retry_count.is_power_of_two() { + Website::render_chrome_page( + &shared.4, &shared.0, + &mut page, url, + &shared.5, + &shared.6, + ) + .await; + } else { + let next_page = Page::new_page_with_cache( + url, + &shared.0, + shared.4.get_cache_options(), + &shared.4.cache_policy, + ) + .await; + + page.clone_from(&next_page) + }; + + }).await + { + log::info!("backoff gateway timeout exceeded {elasped}"); + } + + } else { + + if retry_count.is_power_of_two() { + Website::render_chrome_page( + &shared.4, &shared.0, + &mut page, url, + &shared.5, + &shared.6, + ) + .await; + } else { + page.clone_from( + &Page::new_page_with_cache( + url, + &shared.0, + shared.4.get_cache_options(), + &shared.4.cache_policy, + ) + .await, + ); + } + } + } + + if add_external { + page.set_external( + shared + .4 + .external_domains_caseless + .clone(), + ); + } + + let prev_domain = page.base.take(); + + // Use page's own URL for relative link resolution (not original crawl domain). + // Fixes subdomain pages resolving e.g. href="/path" against wrong host. + page.set_url_parsed_direct(); + let page_base = page.base.take().map(Box::new); + + if return_page_links { + page.page_links = Some(Default::default()); + } + + let (links, bytes_transferred ) = page + .smart_links( + &shared.1, &shared.4, &page_base, &shared.6, Some(&shared.8) + ) + .await; + + page.base = prev_domain; + page.bytes_transferred = bytes_transferred; + + if shared.4.normalize { + page.signature.replace(crate::utils::hash_html(&page.get_html_bytes_u8()).await); + } + + // Run remote multimodal extraction if configured (smart HTTP path) + #[cfg(all(feature = "agent", feature = "serde"))] + if shared.4.remote_multimodal.is_some() { + let html = page.get_html(); + if !html.is_empty() { + use crate::features::automation::{run_remote_multimodal_extraction, AutomationResultExt}; + let title = page.metadata.as_ref().and_then(|m| m.title.as_ref()).map(|t| t.as_str()); + if let Ok(Some(result)) = run_remote_multimodal_extraction( + &shared.4.remote_multimodal, + &html, + url, + title, + ).await { + // Store usage on page + match page.remote_multimodal_usage.as_mut() { + Some(v) => v.push(result.usage.clone()), + None => page.remote_multimodal_usage = Some(vec![result.usage.clone()]), + } + // Store extracted data if available + if result.extracted.is_some() || result.screenshot.is_some() { + let automation_result = result.to_automation_results(); + match page.extra_remote_multimodal_data.as_mut() { + Some(v) => v.push(automation_result), + None => page.extra_remote_multimodal_data = Some(vec![automation_result]), + } + } + } + } + } + + if let Some(ref cb) = on_should_crawl_callback { + if !cb.call(&page) { + page.blocked_crawl = true; + channel_send_page(&shared.2, page, &shared.3); + drop(permit); + return Default::default() + } + } + + let signature = page.signature; + + channel_send_page(&shared.2, page, &shared.3); + + drop(permit); + + (links, signature) + }); + } + + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + } + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res.1 { + Some(signature) => { + if self.is_signature_allowed(signature).await { + self.insert_signature(signature).await; + self.links_visited.extend_links(&mut links, res.0); + } + } + _ => { + self.links_visited.extend_links(&mut links, res.0); + } + } + } else{ + break + } + } + else => break, + } + + if links.is_empty() && set.is_empty() || exceeded_budget { + if exceeded_budget { + while set.join_next().await.is_some() {} + } + break 'outer; + } + } + + self.subscription_guard().await; + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + + if links.is_empty() && set.is_empty() { + break; + } + } + + if !links.is_empty() { + self.extra_links.extend(links); + } + } + } + + /// Sitemap crawl entire lists. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(not(feature = "sitemap"))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub async fn sitemap_crawl( + &mut self, + _client: &Client, + _handle: &Option>, + _scrape: bool, + ) { + } + + /// Sitemap crawl entire lists chain. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(not(feature = "sitemap"))] + pub async fn sitemap_crawl_chain( + &mut self, + _client: &Client, + _handle: &Option>, + _scrape: bool, + ) { + } + + /// Setup the sitemap path + #[cfg(feature = "sitemap")] + pub(crate) fn get_sitemap_setup(&self, domain: &str) -> (&str, bool) { + let (sitemap_path, needs_trailing) = match &self.configuration.sitemap_url { + Some(sitemap_path) => { + let sitemap_path = sitemap_path.as_str(); + if domain.ends_with('/') && sitemap_path.starts_with('/') { + (&sitemap_path[1..], false) + } else if !domain.ends_with('/') + && !sitemap_path.is_empty() + && !sitemap_path.starts_with('/') + { + (sitemap_path, true) + } else { + (sitemap_path, false) + } + } + _ => ("sitemap.xml", !domain.ends_with("/")), + }; + + (sitemap_path, needs_trailing) + } + + /// Sitemap crawl entire lists. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(feature = "sitemap")] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub(crate) async fn sitemap_crawl_raw( + &mut self, + client: &Client, + handle: &Option>, + scrape: bool, + ) { + let mut exceeded_budget = self.is_over_wild_budget(&self.configuration.budget); + + if !exceeded_budget { + let selectors = self.setup_selectors(); + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + let domain = self.url.inner().as_str(); + self.domain_parsed = parse_absolute_url(domain); + + let persist_links = self.status == CrawlStatus::Start; + + let mut interval: Interval = tokio::time::interval(Duration::from_millis(15)); + + let (sitemap_path, needs_trailing) = self.get_sitemap_setup(domain); + + self.configuration.sitemap_url = Some(Box::new( + string_concat!(domain, if needs_trailing { "/" } else { "" }, sitemap_path).into(), + )); + + self.configuration.configure_allowlist(); + + let domain_parsed_ref = self.domain_parsed.as_deref().cloned().map(Box::new); + + let shared = Arc::new(( + self.channel.clone(), + self.channel_guard.clone(), + selectors, + domain_parsed_ref, + )); + let mut sitemaps = match &self.configuration.sitemap_url { + Some(sitemap) => Vec::from([sitemap.to_owned()]), + _ => Default::default(), + }; + + let return_page_links = self.configuration.return_page_links; + + let mut extra_links = self.extra_links.clone(); + self.dequeue(&mut q, &mut extra_links, &mut exceeded_budget) + .await; + self.extra_links.clone_from(&extra_links); + + let whitelist_changes = self.configuration.add_sitemap_to_whitelist(); + + if whitelist_changes.modified() { + self.configuration.set_whitelist(); + } + + 'outer: loop { + let stream = + tokio_stream::iter::>>(sitemaps.drain(..).collect()); + tokio::pin!(stream); + + let mut first_request = false; + let mut attempted_correct = false; + + while let Some(mut sitemap_url) = stream.next().await { + if !self.handle_process(handle, &mut interval, async {}).await { + break 'outer; + } + + let link = ::clone(&(*sitemap_url)).into(); + + let allowed = self.is_allowed_budgetless(&link); + + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + + self.insert_link(link).await; + + let (tx, mut rx) = tokio::sync::mpsc::channel::(100); + + let shared = shared.clone(); + + let handles = crate::utils::spawn_task("page_fetch", async move { + let mut pages = Vec::new(); + + while let Some(mut page) = rx.recv().await { + if page.page_links.is_none() { + let links = page.links(&shared.2, &shared.3).await; + page.page_links = Some(links.into()); + } + + if scrape || persist_links { + pages.push(page.clone()); + }; + + // reset the page links before sending to the main subscriber. + if !return_page_links { + page.page_links = None; + } + + if shared.0.is_some() { + channel_send_page(&shared.0, page, &shared.1); + } + } + + pages + }); + + while !first_request { + // try to get the original sitemap if it had an error on the first request make a request to the root html and parse out the sitemap path. + match client.get(sitemap_url.as_str()).send().await { + Ok(response) => { + let limit = *crate::utils::MAX_SIZE_BYTES as u64; + + if let Some(response_content_length) = response.content_length() { + if limit > 0 && response_content_length >= limit { + // we need a error here + first_request = true; + log::info!( + "{} exceeded parse limit: {:?}", + sitemap_url, + limit + ); + break; + } + } + + if response.status() == 404 { + if !self + .sitemap_parse( + client, + &mut first_request, + &mut sitemap_url, + &mut attempted_correct, + ) + .await + { + break; + } + } else { + match response.bytes().await { + Ok(b) => { + first_request = true; + self.sitemap_parse_crawl( + client, + handle, + b, + &mut interval, + &mut exceeded_budget, + &tx, + &mut sitemaps, + true, + ) + .await; + } + Err(err) => { + first_request = true; + log::info!("http parse error: {:?}", err.to_string()) + } + }; + } + } + Err(err) => { + // do not retry error again. + if attempted_correct { + first_request = true; + break; + } + + log::info!("attempting to find sitemap path: {}", err.to_string()); + + if !self + .sitemap_parse( + client, + &mut first_request, + &mut sitemap_url, + &mut attempted_correct, + ) + .await + { + break; + } + } + }; + } + + drop(tx); + + if let Ok(mut handle) = handles.await { + for page in handle.iter_mut() { + if let Some(mut links) = page.page_links.clone() { + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + self.extra_links.extend(*links) + } + } + if scrape { + if let Some(p) = self.pages.as_mut() { + p.extend(handle); + } + } + } + + if exceeded_budget { + break; + } + } + + if sitemaps.is_empty() || exceeded_budget { + break; + } + } + + self.configuration + .remove_sitemap_from_whitelist(whitelist_changes); + } + } + + /// Sitemap crawl entire lists using chrome. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(all( + feature = "sitemap", + feature = "chrome", + not(feature = "decentralized") + ))] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub(crate) async fn sitemap_crawl_chrome( + &mut self, + client: &Client, + handle: &Option>, + scrape: bool, + ) { + use crate::features::chrome::attempt_navigation; + use sitemap::{ + reader::{SiteMapEntity, SiteMapReader}, + structs::Location, + }; + + let mut exceeded_budget = self.is_over_wild_budget(&self.configuration.budget); + + if !exceeded_budget { + if let Some(mut b) = self.setup_browser().await { + let selectors = self.setup_selectors(); + let semaphore: Arc = self.setup_semaphore(); + let mut q = self.channel_queue.as_ref().map(|q| q.0.subscribe()); + let domain = self.url.inner().as_str(); + self.domain_parsed = parse_absolute_url(&domain); + let persist_links = self.status == CrawlStatus::Start; + + let mut interval = tokio::time::interval(Duration::from_millis(15)); + + let (sitemap_path, needs_trailing) = self.get_sitemap_setup(&domain); + + self.configuration.sitemap_url = Some(Box::new( + string_concat!(domain, if needs_trailing { "/" } else { "" }, sitemap_path) + .into(), + )); + + self.configuration.configure_allowlist(); + + let domain_parsed_ref = self.domain_parsed.as_deref().cloned().map(Box::new); + + let shared = Arc::new(( + self.channel.clone(), + self.channel_guard.clone(), + b.browser.0.clone(), + self.configuration.clone(), + self.url.inner().to_string(), + b.browser.2.clone(), + selectors.clone(), + domain_parsed_ref, + )); + + let mut sitemaps = match &self.configuration.sitemap_url { + Some(sitemap) => Vec::from([sitemap.to_owned()]), + _ => Default::default(), + }; + + let crawl_breaker = if self.configuration.crawl_timeout.is_some() { + Some(Instant::now()) + } else { + None + }; + + let mut extra_links = self.extra_links.clone(); + self.dequeue(&mut q, &mut *extra_links, &mut exceeded_budget) + .await; + self.extra_links.clone_from(&extra_links); + let mut set: JoinSet> = JoinSet::new(); + + let whitelist_changes = self.configuration.add_sitemap_to_whitelist(); + + if whitelist_changes.modified() { + self.configuration.set_whitelist(); + } + + 'outer: loop { + let stream: tokio_stream::Iter>> = + tokio_stream::iter::>>(sitemaps.drain(..).collect()); + tokio::pin!(stream); + + tokio::select! { + biased; + Some(sitemap_url) = stream.next(), if semaphore.available_permits() > 0 && !crawl_duration_expired(&self.configuration.crawl_timeout, &crawl_breaker) => { + if !self.handle_process(handle, &mut interval, async {}).await { + break 'outer; + } + + let link = ::clone(&(*sitemap_url)).into(); + + let allowed = self.is_allowed_budgetless(&link); + + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + + self.insert_link(link).await; + + match attempt_navigation( + "about:blank", + &shared.2, + &self.configuration.request_timeout, + &shared.5, + &self.configuration.viewport, + ) + .await { + Ok(new_page) => { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events( + &new_page, + &self.configuration + ), + self.setup_chrome_interception(&new_page) + ); + + let mut page = Page::new( + &sitemap_url, + &client, + &new_page, + &self.configuration.wait_for, + &self.configuration.screenshot, + false, // we use the initial about:blank page. + &self.configuration.openai_config, + &self.configuration.execution_scripts, + &self.configuration.automation_scripts, + &self.configuration.viewport, + &self.configuration.request_timeout, + &self.configuration.track_events, + self.configuration.referer.clone(), + self.configuration.max_page_bytes, + self.configuration.get_cache_options(), + &self.configuration.cache_policy, + &self.configuration.remote_multimodal, + ) + .await; + + if let Some(h) = intercept_handle { + let abort_handle = h.abort_handle(); + if let Err(elasped) = + tokio::time::timeout(tokio::time::Duration::from_secs(10), h).await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + drop(new_page); + + let is_xml_entry = page.get_html_bytes_u8().starts_with(b""); + + if is_xml { + let reader = SiteMapReader::new(&*page.get_html_bytes_u8()); + let mut stream = tokio_stream::iter(reader); + + while let Some(entity) = stream.next().await { + if !self.handle_process(handle, &mut interval, async {}).await { + break; + } + match entity { + SiteMapEntity::Url(url_entry) => match url_entry.loc { + Location::Url(url) => { + let link: CaseInsensitiveString = url.as_str().into(); + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + + self.insert_link(link.clone()).await; + + let client = client.clone(); + let shared = shared.clone(); + + spawn_set("page_fetch", &mut set, async move { + if let Ok(new_page) = attempt_navigation( + "about:blank", + &shared.2, + &shared.3.request_timeout, + &shared.5, + &shared.3.viewport, + ) + .await + { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events( + &new_page, &shared.3, + ), + crate::features::chrome::setup_chrome_interception_base( + &new_page, + shared.3.chrome_intercept.enabled, + &shared.3.auth_challenge_response, + shared.3.chrome_intercept.block_visuals, + &shared.4, + ) + ); + + let mut page = Page::new( + &link.inner(), + &client, + &new_page, + &shared.3.wait_for, + &shared.3.screenshot, + false, + &shared.3.openai_config, + &shared.3.execution_scripts, + &shared.3.automation_scripts, + &shared.3.viewport, + &shared.3.request_timeout, + &shared.3.track_events, + shared.3.referer.clone(), + shared.3.max_page_bytes, + shared.3.get_cache_options(), + &shared.3.cache_policy, + &shared.3.remote_multimodal, + ) + .await; + + if let Some(intercept_handle) = intercept_handle + { + let abort_handle = + intercept_handle.abort_handle(); + + if let Err(elasped) = tokio::time::timeout( + tokio::time::Duration::from_secs(10), + async { intercept_handle.await }, + ) + .await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if page.page_links.is_none() { + let links = + page.links(&shared.6, &shared.7).await; + page.page_links = Some(links.into()); + } + + Some(page) + } else { + None + } + }); + } + Location::None | Location::ParseErr(_) => (), + }, + SiteMapEntity::SiteMap(sitemap_entry) => { + match sitemap_entry.loc { + Location::Url(url) => { + sitemaps.push(Box::new(CompactString::new( + &url.as_str(), + ))); + } + Location::None | Location::ParseErr(_) => (), + } + } + SiteMapEntity::Err(err) => { + log::info!("incorrect sitemap error: {:?}", err.msg(),) + } + }; + + if exceeded_budget { + break; + } + } + } else { + + if is_xml_entry { + page.modify_xml_html(); + } + + let links = page.links(&shared.6, &shared.7).await; + + let mut stream = tokio_stream::iter(links); + + while let Some(link) = stream.next().await { + if !self.handle_process(handle, &mut interval, async {}).await { + break; + } + + if link.ends_with(".xml") { + sitemaps.push(Box::new(link.inner().clone())); + continue; + } + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + exceeded_budget = true; + break; + } + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + + self.insert_link(link.clone()).await; + + let client = client.clone(); + let shared = shared.clone(); + + spawn_set("page_fetch", &mut set, async move { + match attempt_navigation( + "about:blank", + &shared.2, + &shared.3.request_timeout, + &shared.5, + &shared.3.viewport, + ) + .await { + Ok(new_page) => { + let (_, intercept_handle) = tokio::join!( + crate::features::chrome::setup_chrome_events( + &new_page, &shared.3, + ), + crate::features::chrome::setup_chrome_interception_base( + &new_page, + shared.3.chrome_intercept.enabled, + &shared.3.auth_challenge_response, + shared.3.chrome_intercept.block_visuals, + &shared.4, + ) + ); + + let mut page = Page::new( + &link.inner(), + &client, + &new_page, + &shared.3.wait_for, + &shared.3.screenshot, + false, + &shared.3.openai_config, + &shared.3.execution_scripts, + &shared.3.automation_scripts, + &shared.3.viewport, + &shared.3.request_timeout, + &shared.3.track_events, + shared.3.referer.clone(), + shared.3.max_page_bytes, + shared.3.get_cache_options(), + &shared.3.cache_policy, + &shared.3.remote_multimodal, + ) + .await; + + if let Some(intercept_handle) = intercept_handle { + let abort_handle = intercept_handle.abort_handle(); + + if let Err(elasped) = tokio::time::timeout( + tokio::time::Duration::from_secs(10), + async { intercept_handle.await }, + ) + .await + { + log::warn!("Handler timeout exceeded {elasped}"); + abort_handle.abort(); + } + } + + if page.page_links.is_none() { + let links = page.links(&shared.6, &shared.7).await; + page.page_links = Some(links.into()); + } + + Some(page) + } + Err(err) => { + log::error!("chrome failed to open: {:?}", err); + None + } + } + }); + + if exceeded_budget { + break; + } + } + } + } + Err(err) => { + log::error!("chrome failed to open: {:?}", err); + } + } + + + }, + Some(result) = set.join_next(), if !set.is_empty() => { + if let Ok(res) = result { + match res { + Some(page) => { + if let Some(signature) = page.signature { + if self.is_signature_allowed(signature).await { + if let Some(mut links) = page.page_links.clone() { + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + self.extra_links.extend(*links) + } + self.insert_signature(signature).await; + + channel_send_page( + &shared.0, page.clone(), &shared.1, + ); + + if scrape || persist_links { + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } + } + } else { + if let Some(mut links) = page.page_links.clone() { + self.dequeue(&mut q, &mut links, &mut exceeded_budget).await; + self.extra_links.extend(*links) + } + channel_send_page( + &shared.0, page.clone(), &shared.1, + ); + if scrape || persist_links { + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } + } + } + _ => () + } + } else { + break; + } + } + else => break, + } + + if sitemaps.len() == 0 || exceeded_budget { + break; + } + } + + while let Some(result) = set.join_next().await { + if let Ok(res) = result { + match res { + Some(page) => { + if let Some(signature) = page.signature { + if self.is_signature_allowed(signature).await { + if let Some(mut links) = page.page_links.clone() { + self.dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + self.extra_links.extend(*links) + } + self.insert_signature(signature).await; + channel_send_page(&shared.0, page.clone(), &shared.1); + if scrape || persist_links { + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } + } + } else { + if let Some(mut links) = page.page_links.clone() { + self.dequeue(&mut q, &mut links, &mut exceeded_budget) + .await; + self.extra_links.extend(*links) + } + channel_send_page(&shared.0, page.clone(), &shared.1); + if scrape || persist_links { + if let Some(p) = self.pages.as_mut() { + p.push(page); + } + } + } + } + _ => (), + } + } + } + b.dispose(); + self.configuration + .remove_sitemap_from_whitelist(whitelist_changes); + } + } + } + + /// Sitemap crawl entire lists. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(feature = "sitemap")] + pub async fn sitemap_crawl( + &mut self, + client: &Client, + handle: &Option>, + scrape: bool, + ) { + self.sitemap_crawl_raw(client, handle, scrape).await + } + + /// Sitemap crawl entire lists chain. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(all( + feature = "sitemap", + any(not(feature = "chrome"), feature = "decentralized") + ))] + async fn sitemap_crawl_chain( + &mut self, + client: &Client, + handle: &Option>, + scrape: bool, + ) { + if !self.configuration.ignore_sitemap { + self.sitemap_crawl_raw(client, handle, scrape).await + } + } + + /// Sitemap crawl entire lists chain using chrome. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(all( + feature = "sitemap", + feature = "chrome", + not(feature = "decentralized") + ))] + pub async fn sitemap_crawl_chain( + &mut self, + client: &Client, + handle: &Option>, + scrape: bool, + ) { + if !self.configuration.ignore_sitemap { + self.sitemap_crawl_chrome(client, handle, scrape).await + } + } + + /// Sitemap parse entire lists. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(feature = "sitemap")] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub async fn sitemap_parse( + &mut self, + client: &Client, + first_request: &mut bool, + sitemap_url: &mut Box, + attempted_correct: &mut bool, + ) -> bool { + let mut valid = !*attempted_correct; + + if valid { + if let Some(domain) = &self.domain_parsed { + // attempt to parse the sitemap from the html. + match client.get(domain.as_str()).send().await { + Ok(response) => { + let limit = *crate::utils::MAX_SIZE_BYTES as u64; + + if let Some(response_content_length) = response.content_length() { + if limit > 0 && response_content_length >= limit { + log::info!("{} exceeded parse limit: {:?}", domain, limit); + *first_request = true; + *attempted_correct = true; + valid = false; + } + } + + if valid { + // stream the bytes to lol_html to parse the sitemap from the path. + let cell = tokio::sync::OnceCell::new(); + + let rewriter_settings = lol_html::Settings { + element_content_handlers: vec![lol_html::element!( + r#"link[rel="sitemap"]"#, + |el| { + if let Some(href) = el.get_attribute("href") { + let _ = cell.set(href); + } + Ok(()) + } + )], + adjust_charset_on_meta_tag: false, + ..lol_html::send::Settings::new_for_handler_types() + }; + + let mut rewriter = lol_html::send::HtmlRewriter::new( + rewriter_settings, + |_c: &[u8]| {}, + ); + + let mut wrote_error = false; + let mut stream = response.bytes_stream(); + + while let Some(chunk) = stream.next().await { + if let Ok(chunk) = chunk { + if rewriter.write(&chunk).is_err() { + wrote_error = true; + break; + } + } + if cell.initialized() { + break; + } + } + + if !wrote_error { + let _ = rewriter.end(); + } + + if let Some(sitemap) = cell.get() { + if sitemap.is_empty() { + *first_request = true; + } + + if domain.join(sitemap).is_err() { + *first_request = true; + } + // if we retried the request here it should succeed. + *sitemap_url = Box::new(sitemap.into()); + *attempted_correct = true; + } else { + *first_request = true; + } + } + } + Err(err) => { + *first_request = true; + valid = false; + log::info!("http parse error: {:?}", err.to_string()) + } + }; + } + } + + valid + } + /// Sitemap parse entire lists. Note: this method does not re-crawl the links of the pages found on the sitemap. This does nothing without the `sitemap` flag. + #[cfg(feature = "sitemap")] + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + async fn sitemap_parse_crawl( + &mut self, + client: &Client, + handle: &Option>, + b: bytes::Bytes, + interval: &mut Interval, + exceeded_budget: &mut bool, + tx: &tokio::sync::mpsc::Sender, + sitemaps: &mut Vec>, + crawl: bool, + ) { + use sitemap::reader::{SiteMapEntity, SiteMapReader}; + use sitemap::structs::Location; + + if !b.is_empty() && b.starts_with(b" match url_entry.loc { + Location::Url(url) => { + let link: CaseInsensitiveString = url.as_str().into(); + + let allowed = self.is_allowed(&link); + + if allowed.eq(&ProcessLinkStatus::Blocked) { + continue; + } + + if allowed.eq(&ProcessLinkStatus::BudgetExceeded) { + *exceeded_budget = true; + break; + } + + self.insert_link(link.clone()).await; + + if crawl { + let client = client.clone(); + let tx = tx.clone(); + let cache_options = self.configuration.get_cache_options(); + let cache_policy = self.configuration.cache_policy.clone(); + + crate::utils::spawn_task("page_fetch", async move { + let mut page = Page::new_page_with_cache( + link.inner(), + &client, + cache_options.clone(), + &cache_policy, + ) + .await; + + let mut retry_count = retry; + + while page.should_retry && retry_count > 0 { + if let Some(timeout) = page.get_timeout() { + tokio::time::sleep(timeout).await; + } + page.clone_from( + &Page::new_page_with_cache( + link.inner(), + &client, + cache_options.clone(), + &cache_policy, + ) + .await, + ); + retry_count -= 1; + } + + if let Ok(permit) = tx.reserve().await { + permit.send(page); + } + }); + } + } + Location::None | Location::ParseErr(_) => (), + }, + SiteMapEntity::SiteMap(sitemap_entry) => match sitemap_entry.loc { + Location::Url(url) => { + sitemaps.push(Box::new(CompactString::new(url.as_str()))); + } + Location::None | Location::ParseErr(_) => (), + }, + SiteMapEntity::Err(err) => { + log::info!("incorrect sitemap error: {:?}", err.msg()) + } + }; + + if *exceeded_budget { + break; + } + } + } + } + + /// get base link for crawl establishing. + #[cfg(feature = "regex")] + pub fn get_base_link(&self) -> &CaseInsensitiveString { + &self.url + } + + /// get base link for crawl establishing. + #[cfg(not(feature = "regex"))] + pub fn get_base_link(&self) -> &CompactString { + self.url.inner() + } + + /// Guard the channel from closing until all subscription events complete. + pub async fn subscription_guard(&self) { + if let Some(channel) = &self.channel { + if !channel.1.is_empty() { + if let Some(guard_counter) = &self.channel_guard { + guard_counter.lock().await + } + } + } + } + + /// Launch or connect to browser with setup. + #[cfg(feature = "chrome")] + pub async fn setup_browser_base( + config: &Configuration, + url_parsed: &Option>, + jar: Option<&Arc>, + ) -> Option { + match crate::features::chrome::launch_browser_cookies(config, url_parsed, jar).await { + Some((browser, browser_handle, context_id)) => { + let browser: Arc = Arc::new(browser); + let b = (browser, Some(browser_handle), context_id); + + Some(crate::features::chrome::BrowserController::new(b)) + } + _ => None, + } + } + + /// Launch or connect to browser with setup. + #[cfg(feature = "chrome")] + pub async fn setup_browser(&self) -> Option { + Website::setup_browser_base( + &self.configuration, + self.get_url_parsed(), + Some(&self.cookie_jar), + ) + .await + } + + /// Launch or connect to WebDriver with setup. + #[cfg(feature = "webdriver")] + pub async fn setup_webdriver(&self) -> Option { + crate::features::webdriver::launch_driver(&self.configuration).await + } + + /// Render a page using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn render_webdriver_page( + &self, + url: &str, + driver: &std::sync::Arc, + ) -> Option { + use crate::features::webdriver::{ + attempt_navigation, get_page_content, setup_driver_events, + }; + + let timeout = self + .configuration + .webdriver_config + .as_ref() + .and_then(|c| c.timeout); + + // Navigate to the URL + if let Err(e) = attempt_navigation(url, driver, &timeout).await { + log::error!("WebDriver navigation failed: {:?}", e); + return None; + } + + // Setup events (stealth injection) + setup_driver_events(driver, &self.configuration).await; + + // Get page content + match get_page_content(driver).await { + Ok(content) => Some(content), + Err(e) => { + log::error!("Failed to get WebDriver page content: {:?}", e); + None + } + } + } + + /// Respect robots.txt file. + pub fn with_respect_robots_txt(&mut self, respect_robots_txt: bool) -> &mut Self { + self.configuration + .with_respect_robots_txt(respect_robots_txt); + self + } + + /// Include subdomains detection. + pub fn with_subdomains(&mut self, subdomains: bool) -> &mut Self { + self.configuration.with_subdomains(subdomains); + self + } + + /// Bypass CSP protection detection. This does nothing without the feat flag `chrome` enabled. + pub fn with_csp_bypass(&mut self, enabled: bool) -> &mut Self { + self.configuration.with_csp_bypass(enabled); + self + } + + /// Configure WebDriver for browser automation. This does nothing without the `webdriver` feature flag enabled. + /// When configured, the `crawl()` function will automatically use WebDriver instead of raw HTTP. + #[cfg(feature = "webdriver")] + pub fn with_webdriver( + &mut self, + webdriver_config: crate::features::webdriver_common::WebDriverConfig, + ) -> &mut Self { + self.configuration + .with_webdriver_config(Some(webdriver_config)); + self + } + + /// Configure WebDriver for browser automation. This does nothing without the `webdriver` feature flag enabled. + #[cfg(not(feature = "webdriver"))] + pub fn with_webdriver(&mut self, _webdriver_config: ()) -> &mut Self { + self + } + + /// Use sqlite to store data and track large crawls. This does nothing without the `disk` flag enabled. + #[cfg(feature = "disk")] + pub fn with_sqlite(&mut self, sqlite: bool) -> &mut Self { + if sqlite { + self.enable_sqlite = true; + } else { + self.enable_sqlite = false; + self.sqlite = None; + }; + self + } + + /// Use sqlite to store data and track large crawls. + #[cfg(not(feature = "disk"))] + pub fn with_sqlite(&mut self, _sqlite: bool) -> &mut Self { + self + } + + /// Include tld detection. + pub fn with_tld(&mut self, tld: bool) -> &mut Self { + self.configuration.with_tld(tld); + self + } + + /// The max duration for the crawl. This is useful when websites use a robots.txt with long durations and throttle the timeout removing the full concurrency. + pub fn with_crawl_timeout(&mut self, crawl_timeout: Option) -> &mut Self { + self.configuration.with_crawl_timeout(crawl_timeout); + self + } + + /// Only use HTTP/2. + pub fn with_http2_prior_knowledge(&mut self, http2_prior_knowledge: bool) -> &mut Self { + self.configuration + .with_http2_prior_knowledge(http2_prior_knowledge); + self + } + + /// Delay between request as ms. + pub fn with_delay(&mut self, delay: u64) -> &mut Self { + self.configuration.with_delay(delay); + self + } + + /// Max time to wait for request. + pub fn with_request_timeout(&mut self, request_timeout: Option) -> &mut Self { + self.configuration.with_request_timeout(request_timeout); + self + } + + /// Dangerously accept invalid certificates - this should be used as a last resort. + pub fn with_danger_accept_invalid_certs(&mut self, accept_invalid_certs: bool) -> &mut Self { + self.configuration + .with_danger_accept_invalid_certs(accept_invalid_certs); + self + } + + /// Add user agent to request. + pub fn with_user_agent(&mut self, user_agent: Option<&str>) -> &mut Self { + self.configuration.with_user_agent(user_agent); + self + } + + /// Preserve the HOST header. + pub fn with_preserve_host_header(&mut self, preserve: bool) -> &mut Self { + self.configuration.with_preserve_host_header(preserve); + self + } + + #[cfg(feature = "sitemap")] + /// Add user agent to request. This does nothing without the `sitemap` flag enabled. + pub fn with_sitemap(&mut self, sitemap_url: Option<&str>) -> &mut Self { + self.configuration.with_sitemap(sitemap_url); + self + } + + #[cfg(not(feature = "sitemap"))] + /// Add user agent to request. This does nothing without the `sitemap` flag enabled. + pub fn with_sitemap(&mut self, _sitemap_url: Option<&str>) -> &mut Self { + self + } + + /// Use proxies for request. + pub fn with_proxies(&mut self, proxies: Option>) -> &mut Self { + self.configuration.with_proxies(proxies); + self + } + + /// Use proxies for request with control between chrome and http. + pub fn with_proxies_direct( + &mut self, + proxies: Option>, + ) -> &mut Self { + self.configuration.with_proxies_direct(proxies); + self + } + + /// Set the concurrency limits. If you set the value to None to use the default limits using the system CPU cors * n. + pub fn with_concurrency_limit(&mut self, limit: Option) -> &mut Self { + self.configuration.with_concurrency_limit(limit); + self + } + + /// Set a crawl ID to use for tracking crawls. This does nothing without the `control` flag enabled. + #[cfg(not(feature = "control"))] + pub fn with_crawl_id(&mut self, _crawl_id: String) -> &mut Self { + self + } + + /// Set a crawl ID to use for tracking crawls. This does nothing without the [control] flag enabled. + #[cfg(feature = "control")] + pub fn with_crawl_id(&mut self, crawl_id: String) -> &mut Self { + self.crawl_id = crawl_id.into(); + self + } + + /// Add blacklist urls to ignore. + pub fn with_blacklist_url(&mut self, blacklist_url: Option>) -> &mut Self + where + Vec: From>, + { + self.configuration.with_blacklist_url(blacklist_url); + self + } + + /// Set the retry limit for request. Set the value to 0 for no retries. The default is 0. + pub fn with_retry(&mut self, retry: u8) -> &mut Self { + self.configuration.with_retry(retry); + self + } + + /// Skip setting up a control thread for pause, start, and shutdown programmatic handling. This does nothing without the 'control' flag enabled. + pub fn with_no_control_thread(&mut self, no_control_thread: bool) -> &mut Self { + self.configuration.with_no_control_thread(no_control_thread); + self + } + + /// Add whitelist urls to allow. + pub fn with_whitelist_url(&mut self, whitelist_url: Option>) -> &mut Self + where + Vec: From>, + { + self.configuration.with_whitelist_url(whitelist_url); + self + } + + #[cfg(feature = "chrome")] + /// Track the events made via chrome. + pub fn with_event_tracker( + &mut self, + track_events: Option, + ) -> &mut Self { + self.configuration.with_event_tracker(track_events); + self + } + + /// Set HTTP headers for request using [reqwest::header::HeaderMap](https://docs.rs/reqwest/latest/reqwest/header/struct.HeaderMap.html). + pub fn with_headers(&mut self, headers: Option) -> &mut Self { + self.configuration.with_headers(headers); + self + } + + /// Modify the headers to mimic a real browser. + pub fn with_modify_headers(&mut self, modify_headers: bool) -> &mut Self { + self.configuration.with_modify_headers(modify_headers); + self + } + + /// Modify the HTTP client headers to mimic a real browser. + pub fn with_modify_http_client_headers( + &mut self, + modify_http_client_headers: bool, + ) -> &mut Self { + self.configuration + .with_modify_http_client_headers(modify_http_client_headers); + self + } + + /// Set a crawl budget per path with levels support /a/b/c or for all paths with "*". This does nothing without the `budget` flag enabled. + pub fn with_budget(&mut self, budget: Option>) -> &mut Self { + self.configuration.with_budget(budget); + self + } + + /// Set the crawl budget directly. This does nothing without the `budget` flag enabled. + pub fn set_crawl_budget(&mut self, budget: Option>) { + self.configuration.budget = budget; + } + + /// Set a crawl depth limit. If the value is 0 there is no limit. + pub fn with_depth(&mut self, depth: usize) -> &mut Self { + self.configuration.with_depth(depth); + self + } + + /// Group external domains to treat the crawl as one. If None is passed this will clear all prior domains. + pub fn with_external_domains<'a, 'b>( + &mut self, + external_domains: Option + 'a>, + ) -> &mut Self { + self.configuration.with_external_domains(external_domains); + self + } + + /// Perform a callback to run on each link find. + pub fn with_on_link_find_callback( + &mut self, + on_link_find_callback: Option, + ) -> &mut Self { + match on_link_find_callback { + Some(callback) => self.on_link_find_callback = Some(callback), + _ => self.on_link_find_callback = None, + }; + self + } + + /// Perform a callback to run on each link find shorthand. + pub fn set_on_link_find(&mut self, f: F) + where + F: Fn(CaseInsensitiveString, Option) -> (CaseInsensitiveString, Option) + + Send + + Sync + + 'static, + { + self.on_link_find_callback = Some(Arc::new(f)); + } + + /// Use a callback to determine if a page should be ignored. Return false to ensure that the discovered links are not crawled. + pub fn with_on_should_crawl_callback( + &mut self, + on_should_crawl_callback: Option bool>, + ) -> &mut Self { + match on_should_crawl_callback { + Some(callback) => { + self.on_should_crawl_callback = Some(OnShouldCrawlCallback::Fn(callback)) + } + _ => self.on_should_crawl_callback = None, + }; + self + } + + /// Use an immutable closure to determine if a page should be ignored. Return false to ensure that the discovered links are not crawled. + /// + /// Slightly slower than [`Self::with_on_should_crawl_callback`]. + pub fn with_on_should_crawl_callback_closure( + &mut self, + on_should_crawl_closure: Option, + ) -> &mut Self { + match on_should_crawl_closure { + Some(callback) => { + self.on_should_crawl_callback = + Some(OnShouldCrawlCallback::Closure(Arc::new(callback))) + } + _ => self.on_should_crawl_callback = None, + }; + self + } + + /// Cookie string to use in request. This does nothing without the `cookies` flag enabled. + pub fn with_cookies(&mut self, cookie_str: &str) -> &mut Self { + self.configuration.with_cookies(cookie_str); + self + } + + /// Setup cron jobs to run. This does nothing without the `cron` flag enabled. + pub fn with_cron(&mut self, cron_str: &str, cron_type: CronType) -> &mut Self { + self.configuration.with_cron(cron_str, cron_type); + self + } + + /// Overrides default host system locale with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_locale(&mut self, locale: Option) -> &mut Self { + self.configuration.with_locale(locale); + self + } + + /// Use stealth mode for the request. This does nothing without the `chrome` flag enabled. + pub fn with_stealth(&mut self, stealth_mode: bool) -> &mut Self { + self.configuration.with_stealth(stealth_mode); + self + } + + /// Use stealth mode for the request. This does nothing without the `chrome` flag enabled. + #[cfg(feature = "chrome")] + pub fn with_stealth_advanced( + &mut self, + stealth_mode: spider_fingerprint::configs::Tier, + ) -> &mut Self { + self.configuration.with_stealth_advanced(stealth_mode); + self + } + + /// Set the cache policy. + pub fn with_cache_policy( + &mut self, + cache_policy: Option, + ) -> &mut Self { + self.configuration.with_cache_policy(cache_policy); + + self + } + + /// Use OpenAI to get dynamic javascript to drive the browser. This does nothing without the `openai` flag enabled. + pub fn with_openai(&mut self, openai_configs: Option) -> &mut Self { + self.configuration.with_openai(openai_configs); + self + } + + /// Use a remote multimodal model (vision + HTML + URL) to drive browser automation. + /// + /// When enabled, Spider can ask an OpenAI-compatible “chat completions” endpoint to + /// generate a JSON plan (a list of `WebAutomation` steps), execute those steps against a + /// live Chrome page, then re-capture state and iterate until the model reports it is done + /// (or the configured limits are hit). The default system prompt is set to handle web challenges that can be adjusted if required. + /// Take a look at `DEFAULT_SYSTEM_PROMPT` at spider::features::automation::DEFAULT_SYSTEM_PROMPT for a base line. + /// + /// This is useful for: + /// - handling captchas, + /// - dismissing popups / cookie banners, + /// - navigating to a target page (pricing, docs, etc.), + /// - clicking through multi-step UI flows, + /// - recovering from dynamic page state that plain HTML scraping can’t handle. + /// + /// # Feature gate + /// This method only has an effect when the crate is built with `feature="chrome"`. + /// Without `chrome`, the method is not available. + /// + /// # Parameters + /// - `cfg`: The remote multimodal configuration bundle (endpoint, model, prompts, and runtime knobs). + /// Pass `None` to disable remote multimodal automation. + /// + /// # Example + /// ```no_run + /// # #[cfg(feature = "chrome")] + /// # async fn demo() -> Result<(), Box> { + /// use spider::website::Website; + /// use spider::configuration::Configuration; + /// use spider::features::automation::{RemoteMultimodalConfigs, RemoteMultimodalConfig}; + /// + /// // Build the engine configs (similar to GPTConfigs::new(...)) + /// let mm_cfgs = RemoteMultimodalConfigs::new( + /// "http://localhost:11434/v1/chat/completions", + /// "qwen2.5-vl", // any OpenAI-compatible model id your endpoint understands + /// ) + /// // .with_api_key("your-api-key-if-needed") + /// .with_system_prompt_extra("Never log in. Prefer closing popups and continuing.") + /// .with_user_message_extra("Goal: reach the pricing page, then stop.") + /// .with_cfg(RemoteMultimodalConfig { + /// // keep HTML smaller if you want lower token usage + /// include_html: true, + /// html_max_bytes: 24_000, + /// include_url: true, + /// include_title: true, + /// // loop controls + /// max_rounds: 6, + /// post_plan_wait_ms: 400, + /// ..Default::default() + /// }) + /// .with_concurrency_limit(8); + /// + /// // Attach to the crawler configuration + /// let mut cfg = Configuration::new(); + /// cfg.with_remote_multimodal(Some(mm_cfgs)); + /// + /// // Use the configuration in a Website (example) + /// let mut site = Website::new("https://example.com"); + /// site.with_config(cfg); + /// + /// // Start crawling/scraping as you normally would... + /// // site.crawl().await?; + /// + /// Ok(()) + /// # } + /// ``` + /// + /// # Notes + /// - Remote multimodal automation typically requires `feature="serde"` if you deserialize model + /// steps into `WebAutomation`. + /// - If your endpoint does not support `response_format: {"type":"json_object"}`, disable that + /// in `RemoteMultimodalConfig` (`request_json_object = false`). + #[cfg(feature = "chrome")] + pub fn with_remote_multimodal( + &mut self, + cfg: Option, + ) -> &mut Self { + self.configuration.with_remote_multimodal(cfg); + self + } + + /// Use Gemini to get dynamic javascript to drive the browser. This does nothing without the `gemini` flag enabled. + pub fn with_gemini( + &mut self, + gemini_configs: Option, + ) -> &mut Self { + self.configuration.with_gemini(gemini_configs); + self + } + + /// Cache the page following HTTP rules. This method does nothing if the `cache` feature is not enabled. + pub fn with_caching(&mut self, cache: bool) -> &mut Self { + self.configuration.with_caching(cache); + self + } + + /// Skip browser rendering entirely if cached content exists. + pub fn with_cache_skip_browser(&mut self, skip: bool) -> &mut Self { + self.configuration.with_cache_skip_browser(skip); + self + } + + /// Enable or disable Service Workers. This method does nothing if the `chrome` feature is not enabled. + pub fn with_service_worker_enabled(&mut self, enabled: bool) -> &mut Self { + self.configuration.with_service_worker_enabled(enabled); + self + } + + /// Automatically setup geo-location configurations when using a proxy. This method does nothing if the `chrome` feature is not enabled. + pub fn with_auto_geolocation(&mut self, enabled: bool) -> &mut Self { + self.configuration.with_auto_geolocation(enabled); + self + } + + #[cfg(feature = "chrome")] + /// Set custom fingerprint ID for request. This does nothing without the `chrome` flag enabled. + pub fn with_fingerprint_advanced( + &mut self, + fingerprint: crate::configuration::Fingerprint, + ) -> &mut Self { + self.configuration.with_fingerprint_advanced(fingerprint); + self + } + + /// Setup custom fingerprinting for chrome. This method does nothing if the `chrome` feature is not enabled. + pub fn with_fingerprint(&mut self, fingerprint: bool) -> &mut Self { + self.configuration.with_fingerprint(fingerprint); + self + } + + /// Configures the viewport of the browser, which defaults to 800x600. This method does nothing if the `chrome` feature is not enabled. + pub fn with_viewport(&mut self, viewport: Option) -> &mut Self { + self.configuration.with_viewport(viewport); + self + } + + /// Wait for network request to be idle within a time frame period (500ms no network connections). This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_idle_network( + &mut self, + wait_for_idle_network: Option, + ) -> &mut Self { + self.configuration + .with_wait_for_idle_network(wait_for_idle_network); + self + } + + /// Wait for network request with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_idle_network0( + &mut self, + wait_for_idle_network: Option, + ) -> &mut Self { + self.configuration + .with_wait_for_idle_network0(wait_for_idle_network); + self + } + + /// Wait for network to be almost idle with a max timeout. This does nothing without the `chrome` flag enabled. + pub fn with_wait_for_almost_idle_network0( + &mut self, + wait_for_idle_network: Option, + ) -> &mut Self { + self.configuration + .with_wait_for_almost_idle_network0(wait_for_idle_network); + self + } + + /// Wait for a CSS query selector. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_selector( + &mut self, + wait_for_selector: Option, + ) -> &mut Self { + self.configuration.with_wait_for_selector(wait_for_selector); + self + } + + /// Wait for idle dom mutations for target element. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_idle_dom( + &mut self, + wait_for_selector: Option, + ) -> &mut Self { + self.configuration.with_wait_for_idle_dom(wait_for_selector); + self + } + + /// Wait for a delay. Should only be used for testing. This method does nothing if the `chrome` feature is not enabled. + pub fn with_wait_for_delay( + &mut self, + wait_for_delay: Option, + ) -> &mut Self { + self.configuration.with_wait_for_delay(wait_for_delay); + self + } + + /// The default http connect timeout. + pub fn with_default_http_connect_timeout( + &mut self, + default_http_connect_timeout: Option, + ) -> &mut Self { + self.configuration + .with_default_http_connect_timeout(default_http_connect_timeout); + + self + } + + /// The default http read timeout. + pub fn with_default_http_read_timeout( + &mut self, + default_http_read_timeout: Option, + ) -> &mut Self { + self.configuration + .with_default_http_read_timeout(default_http_read_timeout); + + self + } + + /// Set the max redirects allowed for request. + pub fn with_redirect_limit(&mut self, redirect_limit: usize) -> &mut Self { + self.configuration.with_redirect_limit(redirect_limit); + self + } + + /// Set the redirect policy to use, either Strict or Loose by default. + pub fn with_redirect_policy(&mut self, policy: RedirectPolicy) -> &mut Self { + self.configuration.with_redirect_policy(policy); + self + } + + /// Use request intercept for the request to only allow content that matches the host. If the content is from a 3rd party it needs to be part of our include list. This method does nothing if the `chrome_intercept` flag is not enabled. + pub fn with_chrome_intercept( + &mut self, + chrome_intercept: RequestInterceptConfiguration, + ) -> &mut Self { + self.configuration + .with_chrome_intercept(chrome_intercept, &self.domain_parsed); + self + } + + /// Add a referer to the request. + pub fn with_referer(&mut self, referer: Option) -> &mut Self { + self.configuration.with_referer(referer); + self + } + + /// Add a referer to the request. + pub fn with_referrer(&mut self, referer: Option) -> &mut Self { + self.configuration.with_referrer(referer); + self + } + + /// Determine whether to collect all the resources found on pages. + pub fn with_full_resources(&mut self, full_resources: bool) -> &mut Self { + self.configuration.with_full_resources(full_resources); + self + } + + /// Dismiss all dialogs on the page. This method does nothing if the `chrome` feature is not enabled. + pub fn with_dismiss_dialogs(&mut self, full_resources: bool) -> &mut Self { + self.configuration.with_dismiss_dialogs(full_resources); + self + } + + /// Set the request emuluation. This method does nothing if the `wreq` flag is not enabled. + #[cfg(feature = "wreq")] + pub fn with_emulation(&mut self, emulation: Option) -> &mut Self { + self.configuration.with_emulation(emulation); + self + } + + /// Ignore the sitemap when crawling. This method does nothing if the `sitemap` flag is not enabled. + pub fn with_ignore_sitemap(&mut self, ignore_sitemap: bool) -> &mut Self { + self.configuration.with_ignore_sitemap(ignore_sitemap); + self + } + + /// Overrides default host system timezone with the specified one. This does nothing without the `chrome` flag enabled. + pub fn with_timezone_id(&mut self, timezone_id: Option) -> &mut Self { + self.configuration.with_timezone_id(timezone_id); + self + } + + /// Set a custom script to evaluate on new document creation. This does nothing without the feat flag `chrome` enabled. + pub fn with_evaluate_on_new_document( + &mut self, + evaluate_on_new_document: Option>, + ) -> &mut Self { + self.configuration + .with_evaluate_on_new_document(evaluate_on_new_document); + + self + } + + /// Set a crawl page limit. If the value is 0 there is no limit. + pub fn with_limit(&mut self, limit: u32) -> &mut Self { + self.configuration.with_limit(limit); + self + } + + /// Set the chrome screenshot configuration. This does nothing without the `chrome` flag enabled. + pub fn with_screenshot( + &mut self, + screenshot_config: Option, + ) -> &mut Self { + self.configuration.with_screenshot(screenshot_config); + self + } + + /// Use a shared semaphore to evenly handle workloads. The default is false. + pub fn with_shared_queue(&mut self, shared_queue: bool) -> &mut Self { + self.configuration.with_shared_queue(shared_queue); + self + } + + /// Set the authentiation challenge response. This does nothing without the feat flag `chrome` enabled. + pub fn with_auth_challenge_response( + &mut self, + auth_challenge_response: Option, + ) -> &mut Self { + self.configuration + .with_auth_challenge_response(auth_challenge_response); + self + } + + /// Return the links found on the page in the channel subscriptions. This method does nothing if the `decentralized` is enabled. + pub fn with_return_page_links(&mut self, return_page_links: bool) -> &mut Self { + self.configuration.with_return_page_links(return_page_links); + self + } + + /// Set the connection url for the chrome instance. This method does nothing if the `chrome` is not enabled. + pub fn with_chrome_connection(&mut self, chrome_connection_url: Option) -> &mut Self { + self.configuration + .with_chrome_connection(chrome_connection_url); + self + } + + /// Set JS to run on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_execution_scripts( + &mut self, + execution_scripts: Option, + ) -> &mut Self { + self.configuration.with_execution_scripts(execution_scripts); + self + } + + /// Run web automated actions on certain pages. This method does nothing if the `chrome` is not enabled. + pub fn with_automation_scripts( + &mut self, + automation_scripts: Option, + ) -> &mut Self { + self.configuration + .with_automation_scripts(automation_scripts); + self + } + + /// Bind the connections only on the network interface. + pub fn with_network_interface(&mut self, network_interface: Option) -> &mut Self { + self.configuration.with_network_interface(network_interface); + self + } + + /// Bind to a local IP Address. + pub fn with_local_address(&mut self, local_address: Option) -> &mut Self { + self.configuration.with_local_address(local_address); + self + } + + /// Block assets from loading from the network. Focus primarly on HTML documents. + pub fn with_block_assets(&mut self, only_html: bool) -> &mut Self { + self.configuration.with_block_assets(only_html); + self + } + + /// Normalize the content de-duplicating trailing slash pages and other pages that can be duplicated. This may initially show the link in your links_visited or subscription calls but, the following links will not be crawled. + pub fn with_normalize(&mut self, normalize: bool) -> &mut Self { + self.configuration.with_normalize(normalize); + self + } + + /// Store all the links found on the disk to share the state. This does nothing without the `disk` flag enabled. + pub fn with_shared_state(&mut self, shared: bool) -> &mut Self { + self.configuration.with_shared_state(shared); + self + } + + /// Set the max amount of bytes to collect per page. Only used for chrome atm. + pub fn with_max_page_bytes(&mut self, max_page_bytes: Option) -> &mut Self { + self.configuration.with_max_page_bytes(max_page_bytes); + self + } + + /// Set the max amount of bytes to collected for the browser context. Only used for chrome atm. + pub fn with_max_bytes_allowed(&mut self, max_bytes_allowed: Option) -> &mut Self { + self.configuration.with_max_bytes_allowed(max_bytes_allowed); + self + } + + /// Set the configuration for the website directly. + pub fn with_config(&mut self, config: Configuration) -> &mut Self { + self.configuration = config.into(); + self + } + + /// Set a [spider.cloud](https://spider.cloud) API key (Proxy mode). + #[cfg(feature = "spider_cloud")] + pub fn with_spider_cloud(&mut self, api_key: &str) -> &mut Self { + self.configuration.with_spider_cloud(api_key); + self + } + + /// Set a [spider.cloud](https://spider.cloud) API key (no-op without `spider_cloud` feature). + #[cfg(not(feature = "spider_cloud"))] + pub fn with_spider_cloud(&mut self, _api_key: &str) -> &mut Self { + self + } + + /// Set a [spider.cloud](https://spider.cloud) config. + #[cfg(feature = "spider_cloud")] + pub fn with_spider_cloud_config( + &mut self, + config: crate::configuration::SpiderCloudConfig, + ) -> &mut Self { + self.configuration.with_spider_cloud_config(config); + self + } + + /// Set a [spider.cloud](https://spider.cloud) config (no-op without `spider_cloud` feature). + #[cfg(not(feature = "spider_cloud"))] + pub fn with_spider_cloud_config(&mut self, _config: ()) -> &mut Self { + self + } + + /// Set the hedged request (work-stealing) configuration. + #[cfg(feature = "hedge")] + pub fn with_hedge(&mut self, config: crate::utils::hedge::HedgeConfig) -> &mut Self { + self.configuration.with_hedge(config); + self + } + + /// Set the hedged request configuration (no-op without `hedge` feature). + #[cfg(not(feature = "hedge"))] + pub fn with_hedge(&mut self, _config: ()) -> &mut Self { + self + } + + /// Build the website configuration when using with_builder. + pub fn build(&self) -> Result { + if self.domain_parsed.is_none() { + Err(self.to_owned()) + } else { + Ok(self.to_owned()) + } + } + + /// Clear the HTTP headers for the requests. + pub fn clear_headers(&mut self) { + if let Some(headers) = self.configuration.headers.as_mut() { + headers.0.clear(); + } + } + + /// Determine if the budget has a wildcard path and the depth limit distance. This does nothing without the `budget` flag enabled. + pub fn determine_limits(&mut self) { + self.configuration.configure_budget(); + if self.configuration.inner_budget.is_some() { + let wild_card_budget = match &self.configuration.inner_budget { + Some(budget) => budget.contains_key(&*WILD_CARD_PATH), + _ => false, + }; + self.configuration.wild_card_budgeting = wild_card_budget; + } + if self.configuration.depth > 0 && self.domain_parsed.is_some() { + if let Some(domain) = &self.domain_parsed { + if let Some(segments) = domain.path_segments() { + let segments_cnt = segments.count(); + + if segments_cnt > self.configuration.depth { + self.configuration.depth_distance = self.configuration.depth + + self.configuration.depth.abs_diff(segments_cnt); + } else { + self.configuration.depth_distance = self.configuration.depth; + } + } + } + } + } + + #[cfg(not(feature = "sync"))] + /// Sets up a subscription to receive concurrent data. This will panic if it is larger than `usize::MAX / 2`. + /// Set the value to `0` to use the semaphore permits. If the subscription is going to block or use async methods, + /// make sure to spawn a task to avoid losing messages. This does nothing unless the `sync` flag is enabled. + /// + /// # Examples + /// + /// Subscribe and receive messages using an async tokio environment: + /// + /// ```rust + /// use spider::{tokio, website::Website}; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut website = Website::new("http://example.com"); + /// let mut rx = website.subscribe(0).unwrap(); + /// + /// tokio::spawn(async move { + /// while let Ok(page) = rx.recv().await { + /// tokio::spawn(async move { + /// // Process the received page. + /// // If performing non-blocking tasks or managing a high subscription count, configure accordingly. + /// }); + /// } + /// }); + /// + /// website.crawl().await; + /// } + /// ``` + pub fn subscribe(&mut self, _capacity: usize) -> Option> { + None + } + + /// Sets up a subscription to receive concurrent data. This will panic if it is larger than `usize::MAX / 2`. + /// Set the value to `0` to use the semaphore permits. If the subscription is going to block or use async methods, + /// make sure to spawn a task to avoid losing messages. This does nothing unless the `sync` flag is enabled. + /// + /// # Examples + /// + /// Subscribe and receive messages using an async tokio environment: + /// + /// ```rust + /// use spider::{tokio, website::Website}; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut website = Website::new("http://example.com"); + /// let mut rx = website.subscribe(0).unwrap(); + /// + /// tokio::spawn(async move { + /// while let Ok(page) = rx.recv().await { + /// tokio::spawn(async move { + /// // Process the received page. + /// // If performing non-blocking tasks or managing a high subscription count, configure accordingly. + /// }); + /// } + /// }); + /// + /// website.crawl().await; + /// } + /// ``` + #[cfg(feature = "sync")] + pub fn subscribe(&mut self, capacity: usize) -> Option> { + let channel = self.channel.get_or_insert_with(|| { + let (tx, rx) = broadcast::channel( + (if capacity == 0 { + *DEFAULT_PERMITS + } else { + capacity + }) + .max(1), + ); + (tx, Arc::new(rx)) + }); + + let rx2 = channel.0.subscribe(); + + Some(rx2) + } + + /// Get a sender for queueing extra links mid crawl. This does nothing unless the `sync` flag is enabled. + #[cfg(feature = "sync")] + pub fn queue(&mut self, capacity: usize) -> Option> { + let channel = self.channel_queue.get_or_insert_with(|| { + let (tx, rx) = broadcast::channel(capacity); + (tx, Arc::new(rx)) + }); + + Some(channel.0.to_owned()) + } + + /// Get a sender for queueing extra links mid crawl. This does nothing unless the `sync` flag is enabled. + #[cfg(not(feature = "sync"))] + pub fn queue( + &mut self, + _capacity: usize, + ) -> Option, broadcast::Receiver)>> { + None + } + + /// Remove subscriptions for data. This is useful for auto droping subscriptions that are running on another thread. This does nothing without the `sync` flag enabled. + #[cfg(not(feature = "sync"))] + pub fn unsubscribe(&mut self) {} + + /// Remove subscriptions for data. This is useful for auto droping subscriptions that are running on another thread. This does nothing without the `sync` flag enabled. + #[cfg(feature = "sync")] + pub fn unsubscribe(&mut self) { + self.channel.take(); + } + + /// Get the channel sender to send manual subscriptions. + pub fn get_channel( + &self, + ) -> &Option<(broadcast::Sender, Arc>)> { + &self.channel + } + + /// Get the channel guard to send manual subscriptions from closing. + pub fn get_channel_guard(&self) -> &Option { + &self.channel_guard + } + + /// Setup subscription counter to track concurrent operation completions. + /// This helps keep a chrome instance active until all operations are completed from all threads to safely take screenshots and other actions. + /// Make sure to call `inc` if you take a guard. Without calling `inc` in the subscription receiver the crawl will stay in a infinite loop. + /// This does nothing without the `sync` flag enabled. You also need to use the 'chrome_store_page' to keep the page alive between request. + /// + /// # Example + /// + /// ``` + /// use spider::tokio; + /// use spider::website::Website; + /// #[tokio::main] + /// + /// async fn main() { + /// let mut website: Website = Website::new("http://example.com"); + /// let mut rx2 = website.subscribe(18).unwrap(); + /// let mut rxg = website.subscribe_guard().unwrap(); + /// + /// tokio::spawn(async move { + /// while let Ok(page) = rx2.recv().await { + /// println!("📸 - {:?}", page.get_url()); + /// page + /// .screenshot( + /// true, + /// true, + /// spider::configuration::CaptureScreenshotFormat::Png, + /// Some(75), + /// None::, + /// None, + /// ) + /// .await; + /// rxg.inc(); + /// } + /// }); + /// website.crawl().await; + /// } + /// ``` + #[cfg(not(feature = "sync"))] + pub fn subscribe_guard(&mut self) -> Option { + None + } + + /// Setup subscription counter to track concurrent operation completions. + /// This helps keep a chrome instance active until all operations are completed from all threads to safely take screenshots and other actions. + /// Make sure to call `inc` if you take a guard. Without calling `inc` in the subscription receiver the crawl will stay in a infinite loop. + /// This does nothing without the `sync` flag enabled. You also need to use the 'chrome_store_page' to keep the page alive between request. + /// + /// # Example + /// + /// ``` + /// use spider::tokio; + /// use spider::website::Website; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut website: Website = Website::new("http://example.com"); + /// let mut rx2 = website.subscribe(18).unwrap(); + /// let mut rxg = website.subscribe_guard().unwrap(); + /// + /// tokio::spawn(async move { + /// while let Ok(page) = rx2.recv().await { + /// println!("📸 - {:?}", page.get_url()); + /// page + /// .screenshot( + /// true, + /// true, + /// spider::configuration::CaptureScreenshotFormat::Png, + /// Some(75), + /// None::, + /// None, + /// ) + /// .await; + /// rxg.inc(); + /// } + /// }); + /// website.crawl().await; + /// } + /// ``` + #[cfg(feature = "sync")] + pub fn subscribe_guard(&mut self) -> Option { + // *note*: it would be better to handle this on page drop if the subscription is used automatically. For now we add the API upfront. + let channel_guard = self.channel_guard.get_or_insert_with(ChannelGuard::new); + Some(channel_guard.clone()) + } + + #[cfg(feature = "cron")] + /// Start a cron job - if you use subscribe on another thread you need to abort the handle in conjuction with runner.stop. + pub async fn run_cron(&self) -> Runner { + async_job::Runner::new() + .add(Box::new(self.clone())) + .run() + .await + } + + #[cfg(not(feature = "control"))] + /// Get the attached crawl id. + pub fn get_crawl_id(&self) -> Option<&Box> { + None + } + + #[cfg(feature = "control")] + /// Get the attached crawl id. + pub fn get_crawl_id(&self) -> Option<&Box> { + if self.crawl_id.is_empty() { + None + } else { + Some(&self.crawl_id) + } + } + + /// Set extra useful information. + #[cfg(feature = "extra_information")] + pub fn set_extra_info(&mut self, info: Option) { + self.extra_info = info.map(|f| f.into()); + } + + /// Get extra information stored. + #[cfg(feature = "extra_information")] + pub fn get_extra_info(&self) -> Option<&Box> { + self.extra_info.as_ref() + } + + /// Set the initial HTML page instead of firing a request to the URL. + pub fn set_seeded_html(&mut self, html: Option) { + self.seed_html = html; + } + + /// Get the initial seeded html. + pub fn get_seeded_html(&self) -> &Option { + &self.seed_html + } + + /// Apply configuration from a `PromptConfiguration` generated by an LLM. + /// + /// This method takes a configuration object produced by + /// `RemoteMultimodalEngine::configure_from_prompt()` and applies the + /// settings to this website. + /// + /// # Example + /// ```ignore + /// use spider::features::automation::{RemoteMultimodalEngine, configure_crawler_from_prompt}; + /// + /// let config = configure_crawler_from_prompt( + /// "http://localhost:11434/v1/chat/completions", + /// "llama3", + /// None, + /// "Crawl blog posts only, respect robots.txt, max 100 pages, 200ms delay" + /// ).await?; + /// + /// let mut website = Website::new("https://example.com"); + /// website.apply_prompt_configuration(&config); + /// ``` + #[cfg(feature = "serde")] + pub fn apply_prompt_configuration( + &mut self, + config: &crate::features::automation::PromptConfiguration, + ) -> &mut Self { + // Core crawling + if let Some(v) = config.respect_robots_txt { + self.configuration.respect_robots_txt = v; + } + if let Some(v) = config.subdomains { + self.configuration.subdomains = v; + } + if let Some(v) = config.tld { + self.configuration.tld = v; + } + if let Some(v) = config.depth { + self.configuration.depth = v; + } + if let Some(v) = config.delay { + self.configuration.delay = v; + } + if let Some(ms) = config.request_timeout_ms { + self.configuration.request_timeout = Some(std::time::Duration::from_millis(ms)); + } + if let Some(ms) = config.crawl_timeout_ms { + self.configuration.crawl_timeout = Some(std::time::Duration::from_millis(ms)); + } + + // URL filtering + if let Some(ref urls) = config.blacklist_url { + self.configuration.blacklist_url = + Some(urls.iter().map(|s| s.as_str().into()).collect()); + } + if let Some(ref urls) = config.whitelist_url { + self.configuration.whitelist_url = + Some(urls.iter().map(|s| s.as_str().into()).collect()); + } + if let Some(ref domains) = config.external_domains { + let mut set = (*self.configuration.external_domains_caseless).clone(); + for domain in domains { + set.insert(case_insensitive_string::CaseInsensitiveString::new(domain)); + } + self.configuration.external_domains_caseless = std::sync::Arc::new(set); + } + + // Request settings + if let Some(ref ua) = config.user_agent { + self.configuration.user_agent = Some(Box::new(ua.as_str().into())); + } + if let Some(v) = config.http2_prior_knowledge { + self.configuration.http2_prior_knowledge = v; + } + if let Some(v) = config.accept_invalid_certs { + self.configuration.accept_invalid_certs = v; + } + + // Limits + if let Some(v) = config.redirect_limit { + self.configuration.redirect_limit = v; + } + if let Some(ref budget_map) = config.budget { + let mut budget = hashbrown::HashMap::new(); + for (k, v) in budget_map { + budget.insert(case_insensitive_string::CaseInsensitiveString::new(k), *v); + } + self.configuration.budget = Some(budget); + } + if let Some(v) = config.max_page_bytes { + self.configuration.max_page_bytes = Some(v); + } + + // Content + if let Some(v) = config.full_resources { + self.configuration.full_resources = v; + } + if let Some(v) = config.only_html { + self.configuration.only_html = v; + } + if let Some(v) = config.return_page_links { + self.configuration.return_page_links = v; + } + + // Chrome options + #[cfg(feature = "chrome")] + if let Some(true) = config.use_chrome { + // Chrome is enabled via feature flag, this is a hint for the user + } + if let Some(ref mode) = config.stealth_mode { + self.configuration.stealth_mode = match mode.to_lowercase().as_str() { + "basic" => spider_fingerprint::configs::Tier::Basic, + "low" => spider_fingerprint::configs::Tier::Low, + "mid" => spider_fingerprint::configs::Tier::Mid, + "full" => spider_fingerprint::configs::Tier::Full, + _ => spider_fingerprint::configs::Tier::None, + }; + } + if config.viewport_width.is_some() || config.viewport_height.is_some() { + let width = config.viewport_width.unwrap_or(800); + let height = config.viewport_height.unwrap_or(600); + self.configuration.viewport = Some(crate::configuration::Viewport::new(width, height)); + } + #[cfg(feature = "chrome")] + { + let mut wait_for = self.configuration.wait_for.take().unwrap_or_default(); + + if let Some(true) = config.wait_for_idle_network { + wait_for.idle_network = + Some(crate::features::chrome_common::WaitForIdleNetwork::new( + Some(std::time::Duration::from_secs(30)), + )); + } + if let Some(ms) = config.wait_for_delay_ms { + wait_for.delay = Some(crate::features::chrome_common::WaitForDelay::new(Some( + std::time::Duration::from_millis(ms), + ))); + } + if let Some(ref selector) = config.wait_for_selector { + wait_for.selector = Some(crate::features::chrome_common::WaitForSelector::new( + Some(std::time::Duration::from_secs(30)), + selector.clone(), + )); + } + + if wait_for.idle_network.is_some() + || wait_for.delay.is_some() + || wait_for.selector.is_some() + { + self.configuration.wait_for = Some(wait_for); + } + } + #[cfg(feature = "chrome")] + if let Some(ref js) = config.evaluate_on_new_document { + self.configuration.evaluate_on_new_document = Some(Box::new(js.clone())); + } + + // Performance + if let Some(v) = config.shared_queue { + self.configuration.shared_queue = v; + } + if let Some(v) = config.retry { + self.configuration.retry = v; + } + + self + } + + /// Configure the website from a natural language prompt using an LLM. + /// + /// This is a convenience method that calls the LLM to generate configuration + /// and applies it to the website in one step. + /// + /// # Arguments + /// * `api_url` - OpenAI-compatible chat completions endpoint + /// * `model_name` - Model identifier (e.g., "gpt-4", "llama3", "qwen2.5") + /// * `api_key` - Optional API key for authenticated endpoints + /// * `prompt` - Natural language description of crawling requirements + /// + /// # Example + /// ```ignore + /// let mut website = Website::new("https://example.com"); + /// website.configure_from_prompt( + /// "http://localhost:11434/v1/chat/completions", + /// "llama3", + /// None, + /// "Only crawl product pages, use 100ms delay, max depth 5, respect robots.txt" + /// ).await?; + /// + /// website.crawl().await; + /// ``` + /// Requires the `agent` and `serde` features. + #[cfg(all(feature = "agent", feature = "serde"))] + pub async fn configure_from_prompt( + &mut self, + api_url: &str, + model_name: &str, + api_key: Option<&str>, + prompt: &str, + ) -> Result<&mut Self, crate::features::automation::EngineError> { + let config = crate::features::automation::configure_crawler_from_prompt( + api_url, model_name, api_key, prompt, + ) + .await?; + Ok(self.apply_prompt_configuration(&config)) + } +} + +/// Channel broadcast send the Page to receivers. +pub fn channel_send_page( + channel: &Option<( + tokio::sync::broadcast::Sender, + std::sync::Arc>, + )>, + page: Page, + channel_guard: &Option, +) { + if let Some(c) = channel { + if c.0.send(page).is_ok() { + if let Some(guard) = channel_guard { + ChannelGuard::inc_guard(&guard.0 .1) + } + } + } +} + +/// Guard a channel from closing until all concurrent operations are done. +#[derive(Debug, Clone)] +pub struct ChannelGuard(Arc<(AtomicBool, AtomicUsize, AtomicUsize)>); + +impl ChannelGuard { + /// Create a new channel guard. The tuple has the guard control and the counter. + #[cfg(feature = "sync")] + pub(crate) fn new() -> ChannelGuard { + ChannelGuard(Arc::new(( + AtomicBool::new(true), + AtomicUsize::new(0), + AtomicUsize::new(0), + ))) + } + /// Lock the channel until complete. This is only used for when storing the chrome page outside. + pub(crate) async fn lock(&self) { + if self.0 .0.load(Ordering::Relaxed) { + let old = self.0 .1.load(Ordering::Relaxed); + + while self + .0 + .2 + .compare_exchange_weak(old, 0, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + tokio::task::yield_now().await; + } + std::sync::atomic::fence(Ordering::Acquire); + } + } + + /// Set the guard control manually. If this is set to false the loop will not enter. + pub fn guard(&mut self, guard: bool) { + self.0 .0.store(guard, Ordering::Release); + } + + /// Increment the guard channel completions. + // rename on next major since logic is now flow-controlled. + pub fn inc(&mut self) { + self.0 .2.fetch_add(1, std::sync::atomic::Ordering::Release); + } + + /// Increment a guard channel completions. + pub(crate) fn inc_guard(guard: &AtomicUsize) { + guard.fetch_add(1, std::sync::atomic::Ordering::Release); + } +} + +impl Drop for ChannelGuard { + fn drop(&mut self) { + self.0 .0.store(false, Ordering::Release); + } +} + +#[cfg(feature = "cron")] +/// Start a cron job taking ownership of the website +pub async fn run_cron(website: Website) -> Runner { + async_job::Runner::new().add(Box::new(website)).run().await +} + +#[cfg(feature = "cron")] +#[async_trait] +impl Job for Website { + fn schedule(&self) -> Option { + match self.configuration.cron_str.parse() { + Ok(schedule) => Some(schedule), + Err(e) => { + log::error!("{:?}", e); + None + } + } + } + async fn handle(&mut self) { + log::info!( + "CRON: {} - cron job running {}", + self.get_url().as_ref(), + self.now() + ); + if self.configuration.cron_type == CronType::Crawl { + self.crawl().await; + } else { + self.scrape().await; + } + } +} + +impl std::fmt::Display for Website { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Website:\n URL: {}\n ID: {:?}\n Configuration: {:?}", + self.get_url(), + self.get_crawl_id(), + self.configuration + ) + } +} + +impl std::error::Error for Website {} + +#[tokio::test] +#[cfg(not(feature = "decentralized"))] +async fn crawl() { + let url = "https://choosealicense.com"; + let mut website: Website = Website::new(url); + website.crawl().await; + assert!( + website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); +} + +#[tokio::test] +#[cfg(feature = "cron")] +async fn crawl_cron() { + let url = "https://choosealicense.com"; + let mut website: Website = Website::new(url) + .with_cron("1/5 * * * * *", Default::default()) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + // handle an event on every cron + let join_handle = tokio::spawn(async move { + let mut links_visited = HashSet::new(); + while let Ok(res) = rx2.recv().await { + let url = res.get_url(); + links_visited.insert(CaseInsensitiveString::new(url)); + } + assert!( + links_visited.contains(&CaseInsensitiveString::from( + "https://choosealicense.com/licenses/" + )), + "{:?}", + links_visited + ); + }); + + let mut runner = website.run_cron().await; + log::debug!("Starting the Runner for 10 seconds"); + tokio::time::sleep(Duration::from_secs(10)).await; + runner.stop().await; + join_handle.abort(); + let _ = join_handle.await; +} + +#[tokio::test] +#[cfg(feature = "cron")] +async fn crawl_cron_own() { + let url = "https://choosealicense.com"; + let mut website: Website = Website::new(url) + .with_cron("1/5 * * * * *", Default::default()) + .build() + .unwrap(); + let mut rx2 = website.subscribe(16).unwrap(); + + // handle an event on every cron + let join_handle = tokio::spawn(async move { + let mut links_visited = HashSet::new(); + while let Ok(res) = rx2.recv().await { + let url = res.get_url(); + links_visited.insert(CaseInsensitiveString::new(url)); + } + assert!( + links_visited.contains(&CaseInsensitiveString::from( + "https://choosealicense.com/licenses/" + )), + "{:?}", + links_visited + ); + }); + + let mut runner = run_cron(website).await; + log::debug!("Starting the Runner for 10 seconds"); + tokio::time::sleep(Duration::from_secs(10)).await; + let _ = tokio::join!(runner.stop(), join_handle); +} + +#[tokio::test] +#[cfg(not(feature = "decentralized"))] +async fn scrape() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.scrape().await; + assert!( + website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); + + assert!(!website.get_pages().unwrap()[0].get_html().is_empty()); +} + +#[tokio::test] +#[cfg(not(feature = "decentralized"))] +async fn crawl_invalid() { + let mut website: Website = Website::new("https://w.com"); + website.crawl().await; + assert!(website.links_visited.len() <= 1); // only the target url should exist +} + +#[tokio::test] +#[cfg(feature = "decentralized")] +async fn crawl_invalid() { + let domain = "https://w.com"; + let mut website: Website = Website::new(domain); + website.crawl().await; + let links = website.links_visited.get_links(); + let root = CaseInsensitiveString::from(format!("{}/", domain)); + + assert!(links.contains(&root), "{:?}", links); + + #[cfg(feature = "sitemap")] + { + let sitemap = CaseInsensitiveString::from(format!("{}/sitemap.xml", domain)); + assert!(links.len() <= 2, "{:?}", links); + if links.len() == 2 { + assert!(links.contains(&sitemap), "{:?}", links); + } + } + + #[cfg(not(feature = "sitemap"))] + { + assert_eq!(links.len(), 1, "{:?}", links); + } +} + +#[tokio::test] +async fn not_crawl_blacklist() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.configuration.blacklist_url = Some(Vec::from([CompactString::from( + "https://choosealicense.com/licenses/", + )])); + + website.crawl().await; + assert!( + !website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); +} + +#[tokio::test] +#[cfg(feature = "regex")] +async fn not_crawl_blacklist_regex() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.with_blacklist_url(Some(Vec::from(["choosealicense.com".into()]))); + website.crawl().await; + assert_eq!(website.links_visited.len(), 0); +} + +#[test] +#[cfg(feature = "ua_generator")] +fn randomize_website_agent() { + assert!(!get_ua(false).is_empty()); +} + +#[tokio::test] +#[cfg(not(feature = "decentralized"))] +async fn test_respect_robots_txt() { + let mut website: Website = Website::new("https://stackoverflow.com"); + website.configuration.respect_robots_txt = true; + website.configuration.user_agent = Some(Box::new("*".into())); + + let (client, _): (Client, Option<(Arc, tokio::task::JoinHandle<()>)>) = + website.setup().await; + + website.configure_robots_parser(&client).await; + + assert_eq!(website.configuration.delay, 0); + + assert!(!&website + .is_allowed(&"https://stackoverflow.com/posts/".into()) + .eq(&ProcessLinkStatus::Allowed)); + + // test match for bing bot + let mut website_second: Website = Website::new("https://www.mongodb.com"); + website_second.configuration.respect_robots_txt = true; + website_second.configuration.user_agent = Some(Box::new("bingbot".into())); + + let (client_second, _): (Client, Option<(Arc, tokio::task::JoinHandle<()>)>) = + website_second.setup().await; + website_second.configure_robots_parser(&client_second).await; + + assert!(!&website + .is_allowed(&"https://www.mongodb.com/community/forums/auth/".into()) + .eq(&ProcessLinkStatus::Allowed)); + + // assert_eq!(website_second.configuration.delay, 60000); // should equal one minute in ms +} + +#[tokio::test] +#[cfg(not(feature = "decentralized"))] +#[ignore] +async fn test_crawl_subdomains() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.configuration.subdomains = true; + website.crawl().await; + assert!( + website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); +} + +#[tokio::test] +#[cfg(all( + not(feature = "regex"), + not(feature = "openai"), + not(feature = "gemini") +))] +async fn test_with_configuration() { + let mut website = Website::new("https://choosealicense.com"); + + website + .with_respect_robots_txt(true) + .with_subdomains(true) + .with_tld(false) + .with_delay(0) + .with_request_timeout(None) + .with_http2_prior_knowledge(false) + .with_user_agent(Some(crate::page::TEST_AGENT_NAME)) + .with_headers(None) + .with_proxies(None); + + let mut configuration = Box::new(configuration::Configuration::new()); + + configuration.respect_robots_txt = true; + configuration.subdomains = true; + configuration.tld = false; + configuration.delay = 0; + configuration.request_timeout = None; + configuration.http2_prior_knowledge = false; + configuration.user_agent = Some(Box::new(CompactString::new(crate::page::TEST_AGENT_NAME))); + configuration.headers = None; + configuration.proxies = None; + + assert!( + website.configuration == configuration, + "Left\n{:?}\n\nRight\n{:?}", + website.configuration, + configuration + ); +} + +#[tokio::test] +#[cfg(all(feature = "glob", not(feature = "decentralized")))] +async fn test_crawl_glob() { + let mut website: Website = + Website::new("https://choosealicense.com/licenses/{mit,apache-2.0,mpl-2.0}/"); + website.crawl().await; + + // check for either https/http in collection + assert!( + website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()) + || website + .links_visited + .contains(&"http://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); +} + +#[tokio::test] +#[ignore] +#[cfg(not(feature = "decentralized"))] +async fn test_crawl_tld() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.configuration.tld = true; + website.with_limit(10); + website.crawl().await; + + assert!( + website.links_visited.len() > 1, + "expected more than 1 link visited with tld enabled, got {:?}", + website.links_visited + ); +} + +#[tokio::test] +#[cfg(all(feature = "sync", not(feature = "decentralized")))] +async fn test_crawl_subscription() { + let mut website: Website = Website::new("https://choosealicense.com"); + let mut rx2 = website.subscribe(100).unwrap(); + + let join_handle = tokio::spawn(async move { + let mut count = 0; + + while let Ok(_) = rx2.recv().await { + count += 1; + } + count + }); + + website.crawl().await; + website.unsubscribe(); + let website_links = website.get_links().len(); + let count = join_handle.await.unwrap(); + + // no subscription if did not fulfill. The root page is always captured in links. + assert!(count == website_links, "{:?}", true); +} + +#[tokio::test] +#[cfg(all(feature = "socks", not(feature = "decentralized")))] +async fn test_crawl_proxy() { + let mut website: Website = Website::new("https://choosealicense.com"); + website + .configuration + .proxies + .get_or_insert(Default::default()) + .push("socks5://127.0.0.1:1080".into()); + + website.crawl().await; + + let mut license_found = false; + + for links_visited in website.get_links() { + // Proxy may return http or https in socks5 per platform. + // We may want to replace the protocol with the host of the platform regardless of proxy response. + if links_visited.as_ref().contains("/licenses/") { + license_found = true; + }; + } + + assert!(license_found, "{:?}", website.links_visited); +} + +#[tokio::test] +async fn test_link_duplicates() { + fn has_unique_elements(iter: T) -> bool + where + T: IntoIterator, + T::Item: Eq + std::hash::Hash, + { + let mut uniq = HashSet::new(); + iter.into_iter().all(move |x| uniq.insert(x)) + } + + let mut website: Website = Website::new("http://0.0.0.0:8000"); + website.crawl().await; + + assert!(has_unique_elements(website.links_visited.get_links())); +} + +#[tokio::test] +async fn test_crawl_budget() { + let mut website: Website = Website::new("https://choosealicense.com"); + website.with_budget(Some(HashMap::from([("*", 1), ("/licenses", 1)]))); + website.crawl().await; + + assert!(website.links_visited.len() <= 1); +} + +#[tokio::test] +#[cfg(feature = "control")] +#[ignore] +async fn test_crawl_pause_resume() { + use crate::utils::{pause, resume}; + + let domain = "https://choosealicense.com/"; + let mut website: Website = Website::new(domain); + + let start = tokio::time::Instant::now(); + + tokio::spawn(async move { + pause(domain).await; + // static website test pause/resume - scan will never take longer than 5secs for target website choosealicense + tokio::time::sleep(Duration::from_millis(5000)).await; + resume(domain).await; + }); + + website.crawl().await; + + let duration = start.elapsed(); + + assert!(duration.as_secs() >= 5, "{:?}", duration); + + assert!( + website + .links_visited + .contains(&"https://choosealicense.com/licenses/".into()), + "{:?}", + website.links_visited + ); +} + +#[cfg(feature = "control")] +#[ignore] +#[tokio::test] +async fn test_crawl_shutdown() { + use crate::utils::shutdown; + + // use target blog to prevent shutdown of prior crawler + let domain = "https://spider.cloud/"; + let mut website: Website = Website::new(domain); + + tokio::spawn(async move { + shutdown(domain).await; + }); + + website.crawl().await; + let links_visited_count = website.links_visited.len(); + + assert!(links_visited_count <= 1, "{:?}", links_visited_count); +} + +#[tokio::test] +#[cfg(all(feature = "cache_request", not(feature = "decentralized")))] +async fn test_cache() { + let domain = "https://choosealicense.com/"; + let mut website: Website = Website::new(&domain); + website.configuration.cache = true; + + let fresh_start = tokio::time::Instant::now(); + website.crawl().await; + let fresh_duration = fresh_start.elapsed(); + + let cached_start = tokio::time::Instant::now(); + website.crawl().await; + let cached_duration = cached_start.elapsed(); + + // cache should be faster at least 5x. + assert!( + fresh_duration.as_millis() > cached_duration.as_millis() * 5, + "{:?}", + cached_duration + ); +} + +#[tokio::test] +#[cfg(all( + not(feature = "decentralized"), + feature = "smart", + feature = "cache_chrome_hybrid" +))] +async fn test_crawl_smart_uses_seeded_cache_with_skip_browser() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/cache-smart-test"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = + b"Cached Smart Testcached".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid cache test url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 1)]))); + + let start = tokio::time::Instant::now(); + website.crawl_smart().await; + let elapsed = start.elapsed(); + + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); + assert!(!website.initial_page_should_retry); + assert!( + website.links_visited.contains(&target_url.into()), + "expected smart crawl to visit the cached target" + ); + + eprintln!("crawl_smart cached latency: {}ms", elapsed.as_millis()); +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_shortcircuit_single_page() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/shortcircuit-test"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = + b"Shortcircuit

Cached!

" + .to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 1)]))); + + let mut rx = website.subscribe(4).unwrap(); + let handle = tokio::spawn(async move { rx.recv().await.ok() }); + + let start = tokio::time::Instant::now(); + website.crawl().await; + let elapsed = start.elapsed(); + + let page = handle.await.unwrap().expect("page received via channel"); + assert!( + page.get_html().contains("Cached!"), + "expected cached HTML content" + ); + assert_eq!(page.status_code, StatusCode::OK); + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); + // Must be fast (no browser launch — typically <100ms) + assert!( + elapsed.as_millis() < 2000, + "shortcircuit too slow: {elapsed:?}" + ); + eprintln!( + "shortcircuit single_page latency: {}ms", + elapsed.as_millis() + ); +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_shortcircuit_miss_falls_through() { + let mut website = Website::new("http://localhost:9/uncached-shortcircuit"); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 1)]))); + + // Should not panic, just fall through to normal crawl (will fail to connect, that's OK) + website.crawl_raw().await; +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_shortcircuit_not_without_skip_browser() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/no-skip-shortcircuit"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = b"No Skip".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(false); // NOT skip_browser + website.with_budget(Some(HashMap::from([("*", 1)]))); + + // Verify the shortcircuit guard rejects when skip_browser=false + website.configuration.configure_budget(); + assert!( + !crate::utils::cache_skip_browser(&website.configuration.get_cache_options()), + "cache_skip_browser should be false when skip_browser is disabled" + ); + + // Should still work via normal path (cache hit in Page::new) + website.crawl_raw().await; +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_shortcircuit_not_for_multi_page() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/multi-page-shortcircuit"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = b"Multi Page".to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 5)]))); // NOT single_page + + // Shortcircuit should NOT activate for limit > 1 + // (per-task cache check handles multi-page instead) + website.crawl_raw().await; +} + +#[tokio::test] +#[cfg(all( + not(feature = "decentralized"), + feature = "smart", + feature = "cache_chrome_hybrid" +))] +async fn test_cache_shortcircuit_crawl_smart() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/smart-shortcircuit-test"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = + b"Smart ShortcircuitSmart Cached" + .to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 1)]))); + + let mut rx = website.subscribe(4).unwrap(); + let handle = tokio::spawn(async move { rx.recv().await.ok() }); + + let start = tokio::time::Instant::now(); + website.crawl_smart().await; + let elapsed = start.elapsed(); + + let page = handle.await.unwrap().expect("page received"); + assert!( + page.get_html().contains("Smart Cached"), + "expected cached HTML in crawl_smart" + ); + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); + assert!( + elapsed.as_millis() < 2000, + "crawl_smart shortcircuit too slow: {elapsed:?}" + ); + eprintln!( + "crawl_smart shortcircuit latency: {}ms", + elapsed.as_millis() + ); +} + +#[cfg(test)] +mod tests { + + #[cfg(not(feature = "decentralized"))] + #[test] + fn test_client_rotator_round_robin() { + // Build 3 simple clients to verify round-robin cycling. + let clients: Vec = (0..3) + .map(|_| { + #[cfg(not(feature = "cache_request"))] + { + unsafe { crate::ClientBuilder::new().build().unwrap_unchecked() } + } + #[cfg(feature = "cache_request")] + { + reqwest_middleware::ClientBuilder::new(unsafe { + reqwest::ClientBuilder::new().build().unwrap_unchecked() + }) + .build() + } + }) + .collect(); + + let rotator = crate::website::ClientRotator::new(clients); + assert_eq!(rotator.len(), 3); + assert!(!rotator.is_empty()); + + // Each call to next() should advance the index. + // We verify the pattern cycles by checking the internal index. + let _ = rotator.next(); // index 0 + let _ = rotator.next(); // index 1 + let _ = rotator.next(); // index 2 + let _ = rotator.next(); // index 3 -> wraps to 0 + + // After 4 calls, the atomic index should be 4. + let current_idx = rotator.index.load(crate::website::Ordering::Relaxed); + assert_eq!(current_idx, 4); + } + + #[cfg(not(feature = "decentralized"))] + #[test] + fn test_build_rotated_clients_with_multiple_proxies() { + let mut website = crate::website::Website::new("http://example.com"); + website.configuration.with_proxies(Some(vec![ + "http://proxy1.example.com:8080".to_string(), + "http://proxy2.example.com:8080".to_string(), + "http://proxy3.example.com:8080".to_string(), + ])); + + let rotator = website.build_rotated_clients(); + assert!(rotator.is_some(), "Should build rotator with 3 proxies"); + let rotator = rotator.unwrap(); + assert_eq!(rotator.len(), 3); + } + + #[cfg(not(feature = "decentralized"))] + #[test] + fn test_build_rotated_clients_single_proxy_returns_none() { + let mut website = crate::website::Website::new("http://example.com"); + website + .configuration + .with_proxies(Some(vec!["http://proxy1.example.com:8080".to_string()])); + + let rotator = website.build_rotated_clients(); + assert!( + rotator.is_none(), + "Should not build rotator with only 1 proxy" + ); + } + + #[cfg(not(feature = "decentralized"))] + #[test] + fn test_build_rotated_clients_no_proxies_returns_none() { + let website = crate::website::Website::new("http://example.com"); + let rotator = website.build_rotated_clients(); + assert!( + rotator.is_none(), + "Should not build rotator with no proxies" + ); + } + + fn make_page(status: reqwest::StatusCode) -> crate::page::Page { + let mut page = crate::page::Page::default(); + page.status_code = status; + page + } + + #[test] + fn test_crawl_status_599_empty_page_is_connect_error() { + let mut website = crate::website::Website::new("http://example.com"); + let page = make_page(*crate::page::UNKNOWN_STATUS_ERROR); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ConnectError); + } + + #[test] + fn test_crawl_status_598_empty_page_is_connect_error() { + let mut website = crate::website::Website::new("http://example.com"); + let page = make_page(*crate::page::CHROME_UNKNOWN_STATUS_ERROR); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ConnectError); + } + + #[test] + fn test_crawl_status_598_with_error_status_and_content_is_server_error() { + let mut website = crate::website::Website::new("http://example.com"); + let mut page = make_page(*crate::page::CHROME_UNKNOWN_STATUS_ERROR); + page.html = Some( + b"some content" + .to_vec() + .into_boxed_slice(), + ); + page.error_status = Some("Invalid proxy configuration.".into()); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ServerError); + } + + #[test] + fn test_crawl_status_598_with_error_status_empty_is_connect_error() { + let mut website = crate::website::Website::new("http://example.com"); + let mut page = make_page(*crate::page::CHROME_UNKNOWN_STATUS_ERROR); + page.error_status = Some("Invalid proxy configuration.".into()); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ConnectError); + } + + #[test] + fn test_crawl_status_599_with_content_and_error_status_is_server_error() { + let mut website = crate::website::Website::new("http://example.com"); + let mut page = make_page(*crate::page::UNKNOWN_STATUS_ERROR); + page.html = Some( + b"real server content" + .to_vec() + .into_boxed_slice(), + ); + page.error_status = Some("599 Server Error".into()); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ServerError); + } + + #[test] + fn test_crawl_status_598_with_content_no_error_is_server_error() { + let mut website = crate::website::Website::new("http://example.com"); + let mut page = make_page(*crate::page::CHROME_UNKNOWN_STATUS_ERROR); + page.html = Some( + b"real server content" + .to_vec() + .into_boxed_slice(), + ); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ServerError); + } + + #[test] + fn test_crawl_status_599_with_content_no_error_is_server_error() { + let mut website = crate::website::Website::new("http://example.com"); + let mut page = make_page(*crate::page::UNKNOWN_STATUS_ERROR); + page.html = Some( + b"real server content" + .to_vec() + .into_boxed_slice(), + ); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ServerError); + } + + #[test] + fn test_crawl_status_500_is_server_error() { + let mut website = crate::website::Website::new("http://example.com"); + let page = make_page(reqwest::StatusCode::INTERNAL_SERVER_ERROR); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::ServerError); + } + + #[test] + fn test_crawl_status_429_is_rate_limited() { + let mut website = crate::website::Website::new("http://example.com"); + let page = make_page(reqwest::StatusCode::TOO_MANY_REQUESTS); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::RateLimited); + } + + #[test] + fn test_crawl_status_empty_page_200_is_empty() { + let mut website = crate::website::Website::new("http://example.com"); + let page = make_page(reqwest::StatusCode::OK); + let links = hashbrown::HashSet::new(); + website.set_crawl_initial_status(&page, &links); + assert_eq!(*website.get_status(), super::CrawlStatus::Empty); + } +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_multi_page_all_cached() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-root"; + let sub1_url = "http://localhost:9/cache-phase-sub1"; + let sub2_url = "http://localhost:9/cache-phase-sub2"; + + // Root links to sub1 and sub2 + let root_html = format!( + "Root\ + Sub1\ + Sub2\ + ", + sub1_url, sub2_url + ); + // Sub pages have no outgoing links + let sub1_html = + "Sub1

Sub1 Content

"; + let sub2_html = + "Sub2

Sub2 Content

"; + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + // Seed all three pages into cache + for (url, html) in [ + (root_url, root_html.as_str()), + (sub1_url, sub1_html), + (sub2_url, sub2_html), + ] { + let cache_key = create_cache_key_raw(url, None, None); + let http_response = HttpResponse { + body: html.as_bytes().to_vec(), + headers: response_headers.clone(), + status: 200, + url: Url::parse(url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers.clone()).await; + } + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 10)]))); + + let mut rx = website.subscribe(16).unwrap(); + + website.crawl_raw().await; + + // Collect pages received + let mut pages = Vec::new(); + while let Ok(page) = rx.try_recv() { + pages.push(page.get_url().to_string()); + } + + assert!( + pages.contains(&root_url.to_string()), + "root page should be served from cache" + ); + assert!( + pages.contains(&sub1_url.to_string()), + "sub1 page should be served from cache" + ); + assert!( + pages.contains(&sub2_url.to_string()), + "sub2 page should be served from cache" + ); + assert_eq!(pages.len(), 3, "exactly 3 pages expected"); + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_partial_miss() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-partial-root"; + let sub_url = "http://localhost:9/cache-phase-partial-sub"; + + // Root links to sub (which is NOT in cache) + let root_html = format!( + "Root\ + Sub", + sub_url + ); + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + // Only seed root, NOT sub + let cache_key = create_cache_key_raw(root_url, None, None); + let http_response = HttpResponse { + body: root_html.as_bytes().to_vec(), + headers: response_headers, + status: 200, + url: Url::parse(root_url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 10)]))); + + let mut rx = website.subscribe(16).unwrap(); + + // crawl_raw will: cache phase serves root, puts sub in extra_links, + // then falls through to raw HTTP which fails to connect (localhost:9). + website.crawl_raw().await; + + // Root should have been served from cache + let mut pages = Vec::new(); + while let Ok(page) = rx.try_recv() { + pages.push(page.get_url().to_string()); + } + + assert!( + pages.contains(&root_url.to_string()), + "root page should be served from cache" + ); + // sub_url was NOT cached — it goes to extra_links then raw HTTP (which fails). + // We just verify root was served and no panic occurred. + assert_eq!(website.initial_status_code, StatusCode::OK); +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_skipped_without_skip_browser() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-no-skip"; + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + let cache_key = create_cache_key_raw(root_url, None, None); + let http_response = HttpResponse { + body: b"Cached".to_vec(), + headers: response_headers, + status: 200, + url: Url::parse(root_url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(false); // Deliberately NOT skip_browser + website.with_budget(Some(HashMap::from([("*", 5)]))); + + // Cache phase should not activate without skip_browser + website.configuration.configure_budget(); + assert!( + !crate::utils::cache_skip_browser(&website.configuration.get_cache_options()), + "cache_skip_browser should be false" + ); + + // Should not panic — falls through to normal crawl + website.crawl_raw().await; +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_respects_budget() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-budget"; + let sub1_url = "http://localhost:9/cache-phase-budget-s1"; + let sub2_url = "http://localhost:9/cache-phase-budget-s2"; + + let root_html = format!( + "S1S2", + sub1_url, sub2_url + ); + let sub_html = "Sub"; + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + for (url, html) in [ + (root_url, root_html.as_str()), + (sub1_url, sub_html), + (sub2_url, sub_html), + ] { + let cache_key = create_cache_key_raw(url, None, None); + let http_response = HttpResponse { + body: html.as_bytes().to_vec(), + headers: response_headers.clone(), + status: 200, + url: Url::parse(url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers.clone()).await; + } + + // Budget of 2: root + 1 sub page + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 2)]))); + + let mut rx = website.subscribe(16).unwrap(); + website.crawl_raw().await; + + let mut pages = Vec::new(); + while let Ok(page) = rx.try_recv() { + pages.push(page.get_url().to_string()); + } + + // Budget is 2 so at most 2 pages should be served + assert!( + pages.len() <= 2, + "budget should limit pages to at most 2, got {}", + pages.len() + ); + assert!( + pages.contains(&root_url.to_string()), + "root page should always be served" + ); +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_initial_miss_falls_through() { + // If the initial URL is NOT cached, cache phase returns false and + // falls through to normal crawl (which will fail to connect — that's OK). + let root_url = "http://localhost:9/cache-phase-miss-initial"; + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 5)]))); + + // Should not panic + website.crawl_raw().await; +} + +#[tokio::test] +#[cfg(all(not(feature = "decentralized"), feature = "cache_chrome_hybrid"))] +async fn test_cache_phase_dedup_signatures() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-dedup"; + let dup_url = "http://localhost:9/cache-phase-dedup-dup"; + + // Both pages have identical HTML — normalization should dedup + let html = "Link

Same Content

"; + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + for url in [root_url, dup_url] { + let cache_key = create_cache_key_raw(url, None, None); + let http_response = HttpResponse { + body: html.as_bytes().to_vec(), + headers: response_headers.clone(), + status: 200, + url: Url::parse(url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers.clone()).await; + } + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.configuration.normalize = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 10)]))); + + let mut rx = website.subscribe(16).unwrap(); + website.crawl_raw().await; + + let mut pages = Vec::new(); + while let Ok(page) = rx.try_recv() { + pages.push(page.get_url().to_string()); + } + + // Root always served. Dup should be skipped due to signature dedup. + assert!( + pages.contains(&root_url.to_string()), + "root page should be served" + ); + // The dup page should be filtered by normalize/signature dedup. + // With identical content + normalization, only root should appear. + assert!( + pages.len() <= 2, + "signature dedup should limit duplicate content" + ); +} + +/// Verify that cache_chrome_hybrid_mem properly writes and reads cached entries +/// (regression: `put_hybrid_cache` was no-op without `cache_chrome_hybrid` feature). +#[tokio::test] +#[cfg(all( + not(feature = "decentralized"), + any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem") +))] +async fn test_cache_shortcircuit_single_page_mem() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let target_url = "http://localhost:9/shortcircuit-mem-test"; + let cache_key = create_cache_key_raw(target_url, None, None); + + let mut response_headers = StdHashMap::new(); + response_headers.insert("content-type".to_string(), "text/html".to_string()); + response_headers.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + + let body = b"MemCached

In-Memory Cached!

" + .to_vec(); + let http_response = HttpResponse { + body, + headers: response_headers, + status: 200, + url: Url::parse(target_url).expect("valid url"), + version: HttpVersion::Http11, + }; + + let request_headers = StdHashMap::new(); + + put_hybrid_cache(&cache_key, http_response, "GET", request_headers).await; + + let mut website = Website::new(target_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 1)]))); + + let mut rx = website.subscribe(4).unwrap(); + let handle = tokio::spawn(async move { rx.recv().await.ok() }); + + let start = tokio::time::Instant::now(); + website.crawl().await; + let elapsed = start.elapsed(); + + let page = handle.await.unwrap().expect("page received via channel"); + assert!( + page.get_html().contains("In-Memory Cached!"), + "expected cached HTML content from mem cache" + ); + assert_eq!(page.status_code, StatusCode::OK); + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); + // Must be fast (no browser launch — typically <100ms) + assert!( + elapsed.as_millis() < 2000, + "shortcircuit too slow: {elapsed:?}" + ); + eprintln!( + "shortcircuit single_page (mem) latency: {}ms", + elapsed.as_millis() + ); +} + +/// Verify multi-page cache phase works with cache_chrome_hybrid_mem. +#[tokio::test] +#[cfg(all( + not(feature = "decentralized"), + any(feature = "cache_chrome_hybrid", feature = "cache_chrome_hybrid_mem") +))] +async fn test_cache_phase_multi_page_all_cached_mem() { + use crate::utils::{create_cache_key_raw, put_hybrid_cache, HttpResponse, HttpVersion}; + use std::collections::HashMap as StdHashMap; + + let root_url = "http://localhost:9/cache-phase-mem-root"; + let sub1_url = "http://localhost:9/cache-phase-mem-sub1"; + let sub2_url = "http://localhost:9/cache-phase-mem-sub2"; + + let root_html = format!( + "Root\ + Sub1Sub2", + sub1_url, sub2_url + ); + let sub1_html = + "Sub1

Sub1 Mem Content

"; + let sub2_html = + "Sub2

Sub2 Mem Content

"; + + let request_headers = StdHashMap::new(); + let response_headers = { + let mut h = StdHashMap::new(); + h.insert("content-type".to_string(), "text/html".to_string()); + h.insert( + "cache-control".to_string(), + "public, max-age=3600".to_string(), + ); + h + }; + + for (url, html) in [ + (root_url, root_html.as_str()), + (sub1_url, sub1_html), + (sub2_url, sub2_html), + ] { + let cache_key = create_cache_key_raw(url, None, None); + let http_response = HttpResponse { + body: html.as_bytes().to_vec(), + headers: response_headers.clone(), + status: 200, + url: Url::parse(url).expect("valid url"), + version: HttpVersion::Http11, + }; + put_hybrid_cache(&cache_key, http_response, "GET", request_headers.clone()).await; + } + + let mut website = Website::new(root_url); + website.configuration.cache = true; + website.with_cache_skip_browser(true); + website.with_budget(Some(HashMap::from([("*", 10)]))); + + let mut rx = website.subscribe(16).unwrap(); + website.crawl_raw().await; + + let mut pages = Vec::new(); + while let Ok(page) = rx.try_recv() { + pages.push(page.get_url().to_string()); + } + + assert!( + pages.contains(&root_url.to_string()), + "root page should be served from mem cache" + ); + assert_eq!(pages.len(), 3, "exactly 3 pages expected from mem cache"); + assert_eq!(website.initial_status_code, StatusCode::OK); + assert!(website.initial_html_length > 0); +} diff --git a/spider/tests/chunk_idle_timeout.rs b/spider/tests/chunk_idle_timeout.rs new file mode 100644 index 000000000..6c6d2a6ea --- /dev/null +++ b/spider/tests/chunk_idle_timeout.rs @@ -0,0 +1,161 @@ +use std::io::Write; +use std::net::TcpListener; +use std::time::{Duration, Instant}; + +/// Spin up a local TCP server that sends partial HTTP body then stalls (simulating tarpit / antibot). +fn start_tarpit_server(partial_body: &'static [u8], content_length: usize) -> u16 { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + std::thread::spawn(move || { + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + + // Read the request (consume it so reqwest doesn't hang) + let mut buf = [0u8; 4096]; + let _ = stream.read(&mut buf).unwrap(); + + // Send HTTP headers with Content-Length larger than what we'll send + let headers = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", + content_length + ); + stream.write_all(headers.as_bytes()).unwrap(); + stream.write_all(partial_body).unwrap(); + stream.flush().unwrap(); + + // Now stall forever (tarpit). The idle timeout should kick in. + std::thread::sleep(Duration::from_secs(120)); + } + }); + + port +} + +use std::io::Read; + +#[tokio::test] +async fn chunk_idle_timeout_returns_partial_content() { + // Set a short idle timeout for testing + std::env::set_var("SPIDER_CHUNK_IDLE_TIMEOUT_SECS", "1"); + + let partial_html = b"

Hello

"; + let port = start_tarpit_server(partial_html, 10_000); // Claim 10KB but only send 25 bytes + + let client = spider::reqwest::Client::builder() + .timeout(Duration::from_secs(30)) // Overall timeout is long + .build() + .unwrap(); + + let url = format!("http://127.0.0.1:{}/", port); + + let start = Instant::now(); + let response = client.get(&url).send().await.unwrap(); + + let page_response = spider::utils::handle_response_bytes(response, &url, false).await; + let elapsed = start.elapsed(); + + // Should complete in ~1-2 seconds (idle timeout), NOT 30 seconds (request timeout) + assert!( + elapsed < Duration::from_secs(5), + "should have timed out quickly, took {:?}", + elapsed + ); + + // Partial content should be preserved + let content = page_response.content.expect("should have partial content"); + assert_eq!( + content.as_slice(), + partial_html, + "partial body should be returned" + ); + + // Status code should reflect the original 200 + assert_eq!(page_response.status_code, spider::reqwest::StatusCode::OK); +} + +#[tokio::test] +async fn chunk_idle_timeout_normal_response_unaffected() { + // Set idle timeout + std::env::set_var("SPIDER_CHUNK_IDLE_TIMEOUT_SECS", "2"); + + let full_body = b"

Complete page

"; + let port = start_normal_server(full_body); + + let client = spider::reqwest::Client::builder() + .timeout(Duration::from_secs(10)) + .build() + .unwrap(); + + let url = format!("http://127.0.0.1:{}/", port); + let response = client.get(&url).send().await.unwrap(); + + let page_response = spider::utils::handle_response_bytes(response, &url, false).await; + + let content = page_response.content.expect("should have content"); + assert_eq!(content.as_slice(), full_body); + assert_eq!(page_response.status_code, spider::reqwest::StatusCode::OK); +} + +/// Normal server that sends a complete response immediately. +fn start_normal_server(body: &'static [u8]) -> u16 { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + std::thread::spawn(move || { + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + + let mut buf = [0u8; 4096]; + let _ = stream.read(&mut buf).unwrap(); + + let headers = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", + body.len() + ); + stream.write_all(headers.as_bytes()).unwrap(); + stream.write_all(body).unwrap(); + // Connection closes naturally - no stalling + } + }); + + port +} + +#[tokio::test] +async fn chunk_idle_timeout_disabled_with_zero() { + // Disable idle timeout + std::env::set_var("SPIDER_CHUNK_IDLE_TIMEOUT_SECS", "0"); + + // With timeout disabled and a tarpit, the request should hit the reqwest + // overall timeout instead. We use a very short reqwest timeout to test this. + let partial_html = b"partial"; + let port = start_tarpit_server(partial_html, 50_000); + + let client = spider::reqwest::Client::builder() + .timeout(Duration::from_secs(2)) // Short overall timeout + .build() + .unwrap(); + + let url = format!("http://127.0.0.1:{}/", port); + let start = Instant::now(); + let response = client.get(&url).send().await.unwrap(); + + let page_response = spider::utils::handle_response_bytes(response, &url, false).await; + let elapsed = start.elapsed(); + + // Without chunk idle timeout, it waits for reqwest overall timeout (~2s) + // The partial content should still be salvaged via the Err(e) => break path + assert!( + elapsed >= Duration::from_secs(1), + "without idle timeout, should wait for reqwest timeout" + ); + + // Content may or may not be present depending on how reqwest handles the timeout, + // but the function should not panic + let content = page_response.content.unwrap_or_default(); + // Partial data may have been collected before the error + if !content.is_empty() { + assert_eq!(content.as_slice(), partial_html); + } +} diff --git a/spider/tests/crawler_test_com/canonical_tags.rs b/spider/tests/crawler_test_com/canonical_tags.rs new file mode 100644 index 000000000..efcdfad58 --- /dev/null +++ b/spider/tests/crawler_test_com/canonical_tags.rs @@ -0,0 +1,452 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn canonical_tag_basic() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!(canonical.is_some(), "page should have a canonical tag"); +} + +/// All numbered canonical tag variants (2-25) should be accessible and have canonical tags. +#[tokio::test] +async fn canonical_tag_numbered_variants() { + if !run_live_tests() { + return; + } + + for n in 2..=25 { + let path = format!("/canonical_tags/canonical_tag/{}", n); + let page = fetch_page_http(&path).await; + assert_eq!( + page.status_code, + StatusCode::OK, + "canonical_tag/{} should return 200", + n + ); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!( + canonical.is_some(), + "canonical_tag/{} should have a canonical tag", + n + ); + } +} + +#[tokio::test] +async fn canonical_tag_like_page() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_like_page").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_duplicate_description() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_duplicate_description").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_tag_in_header() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_in_header").await; + assert_eq!(page.status_code, StatusCode::OK); + if let Some(headers) = &page.headers { + let has_link_header = headers.get("link").is_some(); + assert!( + has_link_header, + "canonical_tag_in_header should have a Link HTTP header" + ); + } +} + +#[tokio::test] +async fn canonical_tag_uppercase() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_uppercase").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!( + canonical.is_some(), + "should parse canonical tag even with uppercase" + ); +} + +#[tokio::test] +async fn relative_root_canonical_tag() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/relative_root_canonical_tag").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn relative_canonical_tag() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/relative_canonical_tag").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!(canonical.is_some(), "should have a relative canonical tag"); + let c = canonical.unwrap(); + assert!( + !c.starts_with("http") || c.contains("crawler-test.com"), + "relative canonical: {}", + c + ); +} + +#[tokio::test] +async fn canonical_tag_outside_head() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_outside_head").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_html_header_conflict() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_html_header_conflict").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let html_canonical = extract_canonical(&html); + assert!(html_canonical.is_some(), "should have HTML canonical tag"); +} + +#[tokio::test] +async fn canonical_html_conflict() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_html_conflict").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn page_with_external_canonical() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/page_with_external_canonical").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!(canonical.is_some(), "should have canonical tag"); +} + +#[tokio::test] +async fn page_without_canonical() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/page_without_canonical_tag").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!( + canonical.is_none(), + "page_without_canonical_tag should have no canonical" + ); +} + +#[tokio::test] +async fn unlinked_canonical() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/unlinked_canonical").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn unlinked_canonical_header() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/unlinked_canonical_header").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_tag_og_url_conflict() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_og_url_conflict").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonicalised_to_disallowed_url() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonicalised_to_disallowed_url").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_with_self_reference() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_tag_with_self_reference").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!( + canonical.is_some(), + "should have self-referencing canonical" + ); + let c = canonical.unwrap(); + assert!( + c.contains("canonical_tag_with_self_reference"), + "self-referencing canonical should point to itself: {}", + c + ); +} + +// --- Non-head canonical --- + +#[tokio::test] +async fn non_head_canonical() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/non_head_canonical").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn non_head_canonical_link() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/non_head_canonical_link").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn non_head_canonical_link_2() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/non_head_canonical_link_2").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- Port variants --- + +#[tokio::test] +async fn canonical_port_80() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_port_80").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_port_443() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_port_443").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_port_8080() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_port_8080").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- URL encoding --- + +#[tokio::test] +async fn canonical_url_encoded_vs_non_encoded() { + if !run_live_tests() { + return; + } + + let page = + fetch_page_http("/canonical_tags/canonical_url_encoded_vs_non_encoded/caf%C3%A9").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_url_encoded_emoji() { + if !run_live_tests() { + return; + } + + let page = + fetch_page_http("/canonical_tags/canonical_url_encoded_vs_non_encoded/%F0%9F%8D%BA/ist") + .await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- Parameter / case sensitivity --- + +#[tokio::test] +async fn canonical_parameter_key_case_sensitive() { + if !run_live_tests() { + return; + } + + let page = + fetch_page_http("/canonical_tags/canonical_prameter_key_is_case_sensitive?key=value").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_parameter_value_case_sensitive() { + if !run_live_tests() { + return; + } + + let page = + fetch_page_http("/canonical_tags/canonical_prameter_value_is_case_sensitive?key=value") + .await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_url_fragments() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_url_fragments").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_parameter_order() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/canonical_tags/canonical_different_parameter_order?key2=value2&key=value", + ) + .await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_hostname_case_insensitive() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_hostname_case_insensitive").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_protocol_case_insensitive() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_protocol_case_insensitive").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_path_is_case_sensitive() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_path_is_case_sensitive").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_url_with_slash() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_url_with_slash").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn canonical_trailing_dot() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/canonical_tags/canonical_trailing_dot").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- Chrome --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn canonical_tag_chrome() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/canonical_tags/canonical_tag").await { + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!(canonical.is_some(), "chrome should see canonical tag"); + } else { + eprintln!("SKIP: chrome not available"); + } +} diff --git a/spider/tests/crawler_test_com/content.rs b/spider/tests/crawler_test_com/content.rs new file mode 100644 index 000000000..dfcc03c46 --- /dev/null +++ b/spider/tests/crawler_test_com/content.rs @@ -0,0 +1,284 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn custom_text() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/custom_text").await; + assert_eq!(page.status_code, StatusCode::OK); + assert!( + !page.get_html().is_empty(), + "custom_text should have content" + ); +} + +#[tokio::test] +async fn custom_extraction_text() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/custom_extraction_text").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn above_min_content_volume() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/above_min_content_volume").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn no_h1() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/no_h1").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + !html.contains(" tag" + ); +} + +#[tokio::test] +async fn h1_in_img() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/h1_in_img").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn mult_h1() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/mult_h1").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + let count = html.matches(" 1, + "mult_h1 page should have multiple

tags, got {}", + count + ); +} + +#[tokio::test] +async fn page_html_size() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/page_html_size_n").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + assert!(!html.is_empty(), "page_html_size should have content"); +} + +#[tokio::test] +async fn page_content_size() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/page_content_size_n").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn word_count_100_words() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/word_count_100_words").await; + assert_eq!(page.status_code, StatusCode::OK); + assert!( + !page.get_html().is_empty(), + "word_count page should have content" + ); +} + +#[tokio::test] +async fn word_count_number() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/word_count_number").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn word_count_hyphenated() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/word_count_hyphenated").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn word_count_symbols() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/word_count_symbols").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn word_count_script() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/content/word_count_script").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + html.contains("= 1, "should have at least one title tag"); +} + +#[tokio::test] +async fn title_with_special_chars() { + if !run_live_tests() { + return; + } + + let page = + fetch_page_http("/content/title_with_newline_quote_doublequote_and_comma_characters").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let title = extract_title(&html); + assert!( + title.is_some(), + "should be able to extract title with special chars" + ); +} + +// --- Non-secure form fields --- + +#[tokio::test] +async fn non_secure_form_fields() { + if !run_live_tests() { + return; + } + + let paths = [ + "/content/non_secure_form_fields_text", + "/content/non_secure_form_fields_email", + "/content/non_secure_form_fields_search", + "/content/non_secure_form_fields_number", + "/content/non_secure_form_fields_tel", + "/content/non_secure_form_fields_url", + "/content/non_secure_form_fields_textarea", + "/content/non_secure_form_fields_password_and_cc", + ]; + + for path in &paths { + let page = fetch_page_http(path).await; + assert_eq!( + page.status_code, + StatusCode::OK, + "{} should return 200", + path + ); + } +} + +// --- Chrome --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn content_chrome_renders() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/content/custom_text").await { + assert!( + !page.get_html().is_empty(), + "chrome should render content page" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} diff --git a/spider/tests/crawler_test_com/description_tags.rs b/spider/tests/crawler_test_com/description_tags.rs new file mode 100644 index 000000000..788c44a81 --- /dev/null +++ b/spider/tests/crawler_test_com/description_tags.rs @@ -0,0 +1,110 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn missing_description() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/missing_description").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let desc = extract_meta_description(&html); + if desc.is_some() { + eprintln!("WARN: missing_description has a description: {:?}", desc); + } +} + +#[tokio::test] +async fn no_description_nosnippet() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/no_description_nosnippet").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn description_with_whitespace() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/description_with_whitespace").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let desc = extract_meta_description(&html); + assert!(desc.is_some(), "should have a description tag"); +} + +#[tokio::test] +async fn description_over_max() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/description_over_max").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let desc = extract_meta_description(&html); + assert!(desc.is_some(), "should have a description"); + let d = desc.unwrap(); + assert!( + d.len() > 155, + "description_over_max should be long, got {} chars", + d.len() + ); +} + +#[tokio::test] +async fn short_meta_description() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/short_meta_description").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn duplicate_description() { + if !run_live_tests() { + return; + } + + let page1 = fetch_page_http("/description_tags/duplicate_description").await; + let page2 = fetch_page_http("/description_tags/duplicate_description/foo").await; + assert_eq!(page1.status_code, StatusCode::OK); + assert_eq!(page2.status_code, StatusCode::OK); + + let desc1 = extract_meta_description(&page1.get_html()); + let desc2 = extract_meta_description(&page2.get_html()); + assert_eq!( + desc1, desc2, + "duplicate_description pages should have the same description" + ); +} + +#[tokio::test] +async fn duplicate_description_and_noindex() { + if !run_live_tests() { + return; + } + + let page1 = fetch_page_http("/description_tags/duplicate_description_and_noindex").await; + let page2 = fetch_page_http("/description_tags/duplicate_description_and_noindex/foo").await; + assert_eq!(page1.status_code, StatusCode::OK); + assert_eq!(page2.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn description_http_equiv() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/description_tags/description_http_equiv").await; + assert_eq!(page.status_code, StatusCode::OK); +} diff --git a/spider/tests/crawler_test_com/encoding.rs b/spider/tests/crawler_test_com/encoding.rs new file mode 100644 index 000000000..0375ec914 --- /dev/null +++ b/spider/tests/crawler_test_com/encoding.rs @@ -0,0 +1,213 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn url_with_hebrew_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/%D7%91%D7%9C%D7%94%D7%91%D7%9C%D7%94", + ) + .await; + assert!( + page.status_code.is_success(), + "Hebrew URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_german_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/Zw%C3%B6lf-gro%C3%9Fe-Boxk%C3%A4mpfer-jagen-Viktor-quer-%C3%BCber-den-Sylter-Deich", + ) + .await; + assert!( + page.status_code.is_success(), + "German URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_spanish_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/Fabio-me-exige-sin-tapujos-que-a%C3%B1ada-cerveza-al-whisky", + ) + .await; + assert!( + page.status_code.is_success(), + "Spanish URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_japanese_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%8D%98%E8%AA%9E%E3%81%8C%E9%AB%98%E6%A0%A1%E7%A8%8B%E5%BA%A6%E3%81%AE%E8%BE%9E%E6%9B%B8%E3%81%AB%E8%BC%89%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B", + ) + .await; + assert!( + page.status_code.is_success(), + "Japanese URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_polish_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/pchn%C4%85%C4%87-w-t%C4%99-%C5%82%C3%B3d%C5%BA-je%C5%BCa-lub-o%C5%9Bm-skrzy%C5%84-fig", + ) + .await; + assert!( + page.status_code.is_success(), + "Polish URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_cyrillic_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/%D0%A8%D0%B5%D1%84-%D0%B2%D0%B7%D1%8A%D1%8F%D1%80%D1%91%D0%BD-%D1%82%D1%87%D0%BA-%D1%89%D0%B8%D0%BF%D1%86%D1%8B-%D1%81-%D1%8D%D1%85%D0%BE%D0%BC-%D0%B3%D1%83%D0%B4%D0%B1%D0%B0%D0%B9-%D0%96%D1%8E%D0%BB%D1%8C", + ) + .await; + assert!( + page.status_code.is_success(), + "Cyrillic URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_arabic_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/%EF%B4%BF%D9%85%D8%AD%D9%85%D8%AF-%D8%B1%D8%B3%D9%88%D9%84-%D8%A7%D9%84%D9%84%D9%87-%D9%88%D8%A7%D9%84%D8%B0%D9%8A%D9%86-%D9%85%D8%B9%D9%87-%D8%A3%D8%B4%D8%AF%D8%A7%D8%A1", + ) + .await; + assert!( + page.status_code.is_success(), + "Arabic URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_greek_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/%CE%B3%CF%81%CE%AC%CE%BC%CE%BC%CE%B1%CF%84%CE%B1-%CF%84%CE%BF%CF%85-%CE%B9%CF%83%CF%80%CE%B1%CE%BD%CE%B9%CE%BA%CE%BF%CF%8D-%CE%B1%CE%BB%CF%86%CE%B1%CE%B2%CE%AE%CF%84%CE%BF%CF%85-%CE%BA%CE%B1%CE%B8%CF%8E%CF%82", + ) + .await; + assert!( + page.status_code.is_success(), + "Greek URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn url_with_scandinavian_characters() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/url_with_foreign_characters/as%C3%B8d-%C3%A6ada-%C3%A5djghf-g%C3%A4gfd-as%C3%B6dsads", + ) + .await; + assert!( + page.status_code.is_success(), + "Scandinavian URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn double_encoded_url() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http( + "/encoding/double_encoded_url/Zw%25C3%25B6lf-gro%25C3%259Fe-Boxk%25C3%25A4mpfer-jagen-Viktor-quer-%25C3%25BCber-den-Sylter-Deich", + ) + .await; + assert!( + page.status_code.is_success(), + "double encoded URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn inconsistent_character_encoding() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/encoding/inconsistent_character_encoding").await; + assert_eq!(page.status_code, StatusCode::OK); + let _html = page.get_html(); +} + +#[tokio::test] +async fn encoded_hashbang() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/encoding/url/encoded_hashbang%23abc").await; + assert!( + page.status_code.is_success(), + "encoded hashbang URL should resolve, got {}", + page.status_code + ); +} + +#[tokio::test] +async fn page_titles_character_encoded() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/encoding/page_titles_character_encoded").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let title = extract_title(&html); + assert!( + title.is_some(), + "should extract title even with character encoding" + ); +} diff --git a/spider/tests/crawler_test_com/helpers.rs b/spider/tests/crawler_test_com/helpers.rs new file mode 100644 index 000000000..05fe2dbdd --- /dev/null +++ b/spider/tests/crawler_test_com/helpers.rs @@ -0,0 +1,212 @@ +use spider::hashbrown::HashMap; +use spider::page::Page; +use spider::website::Website; +use std::env; +use std::time::Duration; + +pub const BASE: &str = "https://www.crawler-test.com"; +const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); +const CRAWL_TIMEOUT: Duration = Duration::from_secs(60); + +pub fn run_live_tests() -> bool { + matches!( + env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +/// Fetch a single page via HTTP (no browser). +pub async fn fetch_page_http(path: &str) -> Page { + let url = format!("{}{}", BASE, path); + let client = spider::reqwest::Client::builder() + .timeout(REQUEST_TIMEOUT) + .redirect(spider::reqwest::redirect::Policy::limited(10)) + .build() + .expect("build http client"); + Page::new_page(&url, &client).await +} + +/// Fetch a single page via HTTP with no redirect following. +pub async fn fetch_page_http_no_redirect(path: &str) -> Page { + let url = format!("{}{}", BASE, path); + let client = spider::reqwest::Client::builder() + .timeout(REQUEST_TIMEOUT) + .redirect(spider::reqwest::redirect::Policy::none()) + .build() + .expect("build http client (no redirect)"); + Page::new_page(&url, &client).await +} + +/// Crawl multiple pages via HTTP, collecting results lock-free through broadcast channels. +/// Uses tokio::join! + tokio::select! pattern (same as spider's scrape_raw). +pub async fn crawl_collect_http(path: &str, budget: u32, depth: usize) -> Vec { + let url = format!("{}{}", BASE, path); + let mut website = Website::new(&url); + website + .with_budget(Some(HashMap::from([("*", budget)]))) + .with_depth(depth) + .with_request_timeout(Some(REQUEST_TIMEOUT)) + .with_crawl_timeout(Some(CRAWL_TIMEOUT)); + + let mut w = website.clone(); + let mut rx = w.subscribe(16).expect("subscribe"); + let (done_tx, mut done_rx) = spider::tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_raw().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut pages = Vec::new(); + let sub = async { + loop { + spider::tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(page) = result { + pages.push(page); + } else { + break; + } + } + } + } + }; + + spider::tokio::join!(sub, crawl); + pages +} + +/// Crawl and collect pages using smart mode. +#[cfg(feature = "smart")] +pub async fn crawl_collect_smart(path: &str, budget: u32, depth: usize) -> Vec { + let url = format!("{}{}", BASE, path); + let mut website = Website::new(&url); + website + .with_budget(Some(HashMap::from([("*", budget)]))) + .with_depth(depth) + .with_request_timeout(Some(REQUEST_TIMEOUT)) + .with_crawl_timeout(Some(CRAWL_TIMEOUT)); + + let mut w = website.clone(); + let mut rx = w.subscribe(16).expect("subscribe"); + let (done_tx, mut done_rx) = spider::tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_smart().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut pages = Vec::new(); + let sub = async { + loop { + spider::tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(page) = result { + pages.push(page); + } else { + break; + } + } + } + } + }; + + spider::tokio::join!(sub, crawl); + pages +} + +/// Fetch a single page via Chrome using subscribe + crawl. +/// Uses the same tokio::join! + select! pattern as scrape_raw. +#[cfg(feature = "chrome")] +pub async fn fetch_page_chrome(path: &str) -> Option { + let url = format!("{}{}", BASE, path); + let mut website = Website::new(&url); + website + .with_limit(1) + .with_depth(0) + .with_request_timeout(Some(REQUEST_TIMEOUT)) + .with_crawl_timeout(Some(CRAWL_TIMEOUT)); + + let mut w = website.clone(); + let mut rx = w.subscribe(4).expect("subscribe"); + let (done_tx, mut done_rx) = spider::tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut page = None; + let sub = async { + loop { + spider::tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(p) = result { + page = Some(p); + } else { + break; + } + } + } + } + }; + + spider::tokio::join!(sub, crawl); + page +} + +/// Extract text content between specific HTML tags using simple string search. +pub fn extract_tag_content(html: &str, tag: &str) -> Option { + let open = format!("<{}", tag); + let close = format!("", tag.split_whitespace().next().unwrap_or(tag)); + let start = html.find(&open)?; + let after_open = html[start..].find('>')?; + let content_start = start + after_open + 1; + let end = html[content_start..].find(&close)?; + Some(html[content_start..content_start + end].trim().to_string()) +} + +/// Extract canonical URL from HTML. +pub fn extract_canonical(html: &str) -> Option { + let lower = html.to_lowercase(); + let idx = lower.find("rel=\"canonical\"")?; + let snippet_start = if idx > 200 { idx - 200 } else { 0 }; + let snippet_end = (idx + 200).min(html.len()); + let snippet = &html[snippet_start..snippet_end]; + let href_idx = snippet.to_lowercase().find("href=\"")?; + let href_start = href_idx + 6; + let href_end = snippet[href_start..].find('"')?; + Some(snippet[href_start..href_start + href_end].to_string()) +} + +/// Extract meta description from HTML. +pub fn extract_meta_description(html: &str) -> Option { + let lower = html.to_lowercase(); + let idx = lower.find("name=\"description\"")?; + let snippet_start = if idx > 300 { idx - 300 } else { 0 }; + let snippet_end = (idx + 300).min(html.len()); + let snippet = &html[snippet_start..snippet_end]; + let lower_snippet = snippet.to_lowercase(); + let content_idx = lower_snippet.find("content=\"")?; + let content_start = content_idx + 9; + let content_end = snippet[content_start..].find('"')?; + Some(snippet[content_start..content_start + content_end].to_string()) +} + +/// Extract page title from HTML. +pub fn extract_title(html: &str) -> Option { + extract_tag_content(html, "title") +} diff --git a/spider/tests/crawler_test_com/javascript.rs b/spider/tests/crawler_test_com/javascript.rs new file mode 100644 index 000000000..86df8b7d6 --- /dev/null +++ b/spider/tests/crawler_test_com/javascript.rs @@ -0,0 +1,503 @@ +//! JavaScript tests — Chrome/Smart mode only. +//! These pages require JS execution to render their content. + +use crate::helpers::*; + +// --- Chrome-mode tests --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn dynamically_inserted_text() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/dynamically-inserted-text").await { + let html = page.get_html(); + assert!( + html.len() > 100, + "chrome should render dynamically inserted text" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn dynamically_inserted_text_meta_data() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/dynamically-inserted-text-meta-data").await { + assert!( + !page.get_html().is_empty(), + "chrome should render dynamically inserted meta data" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn ajax_return_data() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/ajax-return-data").await { + let html = page.get_html(); + assert!(html.len() > 100, "chrome should wait for AJAX data"); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn onload_added_title() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/onload-added-title").await { + let html = page.get_html(); + let title = extract_title(&html); + assert!( + title.is_some() && !title.as_ref().unwrap().is_empty(), + "chrome should see dynamically added title" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn onload_inserted_canonical() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/onload-inserted-canonical").await { + let html = page.get_html(); + let canonical = extract_canonical(&html); + assert!( + canonical.is_some(), + "chrome should see dynamically inserted canonical" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn dynamically_inserted_nofollow() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/dynamically-inserted-nofollow").await { + let html = page.get_html().to_lowercase(); + assert!( + html.contains("nofollow") || html.contains("rel="), + "chrome should see dynamically inserted nofollow" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +// --- Renderer timeouts --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn renderer_timeout_1() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/renderer_timeout/1"), + ) + .await; + assert!(result.is_ok(), "renderer_timeout/1 should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn renderer_timeout_2() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/renderer_timeout/2"), + ) + .await; + assert!(result.is_ok(), "renderer_timeout/2 should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn renderer_timeout_3() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/renderer_timeout/3"), + ) + .await; + assert!(result.is_ok(), "renderer_timeout/3 should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn renderer_timeout_4() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/renderer_timeout/4"), + ) + .await; + assert!(result.is_ok(), "renderer_timeout/4 should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn renderer_timeout_5() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/renderer_timeout/5"), + ) + .await; + assert!(result.is_ok(), "renderer_timeout/5 should complete"); +} + +// --- Window location redirects --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_redirect_internal() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/window-location-internal"), + ) + .await; + assert!(result.is_ok(), "JS redirect should complete in chrome"); + if let Some(page) = result.unwrap() { + assert!( + !page.get_html().is_empty(), + "JS redirect should produce content" + ); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_redirect_external() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/window-location-external"), + ) + .await; + assert!(result.is_ok(), "external JS redirect should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_function_absolute() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/window-location-function-absolute"), + ) + .await; + assert!(result.is_ok(), "absolute JS redirect should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_function_relative() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/window-location-function-relative"), + ) + .await; + assert!(result.is_ok(), "relative JS redirect should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_onchange() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/window-location-onchange"), + ) + .await; + assert!(result.is_ok(), "onchange JS redirect should complete"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_location_onclick() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/window-location-onclick").await { + assert!( + !page.get_html().is_empty(), + "onclick page should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn window_open() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/window-open").await { + assert!( + !page.get_html().is_empty(), + "window-open should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +// --- JS link discovery --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn onmousedown() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/onmousedown").await { + assert!( + !page.get_html().is_empty(), + "onmousedown should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn concatenatedlink() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/concatenatedlink").await { + assert!( + !page.get_html().is_empty(), + "concatenatedlink should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn data_hreflink() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/data-hreflink").await { + assert!( + !page.get_html().is_empty(), + "data-hreflink should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn push_state() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/push_state").await { + assert!( + !page.get_html().is_empty(), + "push_state should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn onclick_reveals_element() { + if !run_live_tests() { + return; + } + + if let Some(page) = + fetch_page_chrome("/javascript/onclick-reveals-element-programmatically-added-onclick") + .await + { + assert!( + !page.get_html().is_empty(), + "onclick-reveals should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +// --- Dialogs --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn dialog_window() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/dialog_window"), + ) + .await; + assert!(result.is_ok(), "dialog_window should not hang"); +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn alert_box() { + if !run_live_tests() { + return; + } + + let result = spider::tokio::time::timeout( + std::time::Duration::from_secs(30), + fetch_page_chrome("/javascript/alert_box"), + ) + .await; + assert!(result.is_ok(), "alert_box should not hang"); +} + +// --- Script blocking --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn ad_script() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/ad_script").await { + assert!(!page.get_html().is_empty(), "ad_script should have content"); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn analytics_script() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/javascript/analytics_script").await { + assert!( + !page.get_html().is_empty(), + "analytics_script should have content" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +// --- HTTP-only test: JS pages should have script tags without a browser --- + +#[tokio::test] +async fn js_page_http_has_script_tags() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/javascript/dynamically-inserted-text").await; + let html = page.get_html().to_lowercase(); + assert!( + html.contains(" 100, "smart mode should render JS content"); +} + +#[cfg(feature = "smart")] +#[tokio::test] +async fn ajax_return_data_smart() { + if !run_live_tests() { + return; + } + + let pages = crawl_collect_smart("/javascript/ajax-return-data", 1, 0).await; + assert!(!pages.is_empty(), "smart mode should fetch AJAX page"); +} diff --git a/spider/tests/crawler_test_com/links.rs b/spider/tests/crawler_test_com/links.rs new file mode 100644 index 000000000..2233ebd06 --- /dev/null +++ b/spider/tests/crawler_test_com/links.rs @@ -0,0 +1,297 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn broken_links_internal_page_exists() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/links/broken_links_internal").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + assert!( + html.contains(" tag for relative URL resolution" + ); +} + +#[tokio::test] +async fn image_links() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/links/image_links").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + html.contains(". +//! +//! Gated behind `RUN_LIVE_TESTS` env var. Requires `basic` feature at minimum. +//! Smart-mode tests require `smart` feature. Chrome-mode tests require `chrome` feature. +//! +//! Run: +//! RUN_LIVE_TESTS=1 cargo test --test crawler_test_com --features "basic" +//! RUN_LIVE_TESTS=1 cargo test --test crawler_test_com --features "basic,smart" +//! RUN_LIVE_TESTS=1 cargo test --test crawler_test_com --features "basic,chrome" + +mod helpers; + +mod canonical_tags; +mod content; +mod description_tags; +mod encoding; +mod javascript; +mod links; +mod mobile; +mod other; +mod redirects; +mod robots_protocol; +mod social_tags; +mod status_codes; +mod titles; +mod urls; diff --git a/spider/tests/crawler_test_com/mobile.rs b/spider/tests/crawler_test_com/mobile.rs new file mode 100644 index 000000000..ff30107c9 --- /dev/null +++ b/spider/tests/crawler_test_com/mobile.rs @@ -0,0 +1,293 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn responsive() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/responsive").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + html.contains("viewport"), + "responsive page should have a viewport meta tag" + ); +} + +#[tokio::test] +async fn dynamic_mobile() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/dynamic").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn no_mobile_configuration() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/no_mobile_configuration").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn responsive_with_amp() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/responsive_with_amp").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + html.contains("amphtml") || html.contains("viewport"), + "responsive_with_amp should have amp or viewport markers" + ); +} + +// --- Separate desktop variants --- + +#[tokio::test] +async fn separate_desktop_with_different_h1() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_different_h1").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_with_different_title() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_different_title").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_with_different_wordcount() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_different_wordcount").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_with_different_links_in() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_different_links_in").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_with_different_links_out() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_different_links_out").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_with_mobile_not_subdomain() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_with_mobile_not_subdomain").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_mobile_with_mobile_not_subdomain() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_mobile_with_mobile_not_subdomain").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_irregular_media() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_irregular_media").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_desktop_response_header_alt() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_desktop_response_header_alt").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- AMP variants --- + +#[tokio::test] +async fn desktop_with_amp_as_mobile() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/desktop_with_AMP_as_mobile").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn desktop_with_self_canonical_mobile_and_amp() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/desktop_with_self_canonical_mobile_and_amp").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn other_desktop_that_links_to_same_mobile_pages() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/other_desktop_that_links_to_the_same_mobile_pages").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn amp_with_separate_mobile() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/amp_with_separate_mobile").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn amp_with_responsive() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/amp_with_responsive").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn no_mobile_with_amp() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/no_mobile_with_amp").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn amp_with_no_mobile() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/amp_with_no_mobile").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn amp_no_references() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/amp_no_references").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn amp_as_desktop_amp_and_mobile() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/amp_as_desktop_amp_and_mobile").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn separate_amp_with_self_canonical() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/mobile/separate_amp_with_self_canonical").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +// --- Chrome --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn responsive_chrome() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/mobile/responsive").await { + let html = page.get_html().to_lowercase(); + assert!( + html.contains("viewport"), + "chrome should see viewport meta tag" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn dynamic_mobile_chrome() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/mobile/dynamic").await { + assert!( + !page.get_html().is_empty(), + "chrome should render dynamic mobile page" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} diff --git a/spider/tests/crawler_test_com/other.rs b/spider/tests/crawler_test_com/other.rs new file mode 100644 index 000000000..0ff18ca19 --- /dev/null +++ b/spider/tests/crawler_test_com/other.rs @@ -0,0 +1,248 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn crawler_user_agent() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/crawler_user_agent").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + assert!(!html.is_empty(), "user_agent page should echo back the UA"); +} + +#[tokio::test] +async fn crawler_ip_address() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/crawler_ip_address").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn crawler_request_headers() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/crawler_request_headers").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + assert!( + !html.is_empty(), + "request_headers page should echo back headers" + ); +} + +#[tokio::test] +async fn page_with_hsts_headers() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/page_with_hsts_headers").await; + assert_eq!(page.status_code, StatusCode::OK); + if let Some(headers) = &page.headers { + let has_hsts = headers.get("strict-transport-security").is_some(); + assert!(has_hsts, "should have HSTS header"); + } +} + +#[tokio::test] +async fn page_load_time_n() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/page_load_time_n").await; + assert!( + page.status_code.is_success() || page.status_code == StatusCode::NOT_FOUND, + "page_load_time_n: got {}", + page.status_code + ); +} + +#[tokio::test] +async fn typo_in_head() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/typo_in_head").await; + assert_eq!(page.status_code, StatusCode::OK); + assert!( + !page.get_html().is_empty(), + "typo in head should not prevent HTML parsing" + ); +} + +#[tokio::test] +async fn unfinished_tag_in_head() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/unfinished_tag_in_head").await; + assert_eq!(page.status_code, StatusCode::OK); + assert!( + !page.get_html().is_empty(), + "unfinished tag should not prevent parsing" + ); +} + +#[tokio::test] +async fn non_head_tag_in_head() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/non_head_tag_in_head").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn link_tag_in_body() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/link_tag_in_body").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn script_tag_contents() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/other/script_tag_contents").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!(html.contains(" = mismatches + .iter() + .filter(|(expected, _)| matches!(expected, 200 | 301 | 400 | 403 | 404 | 500 | 503)) + .collect(); + assert!( + critical_mismatches.is_empty(), + "critical status codes must match: {:?}", + critical_mismatches + ); +} + +/// Verify status codes return correct values when following redirects. +#[tokio::test] +async fn status_codes_with_redirect_follow() { + if !run_live_tests() { + return; + } + + // 2xx codes should return success when following redirects + let success_codes: &[u16] = &[200, 201, 202, 203, 205, 206, 207, 226]; + for &code in success_codes { + let path = format!("/status_codes/status_{}", code); + let page = fetch_page_http(&path).await; + assert!( + page.status_code.is_success() || page.status_code.as_u16() == code, + "status_{} with redirect follow: got {}", + code, + page.status_code + ); + } +} + +#[tokio::test] +async fn status_200_has_html_body() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/status_codes/status_200").await; + assert_eq!(page.status_code, StatusCode::OK); + assert!( + !page.get_html().is_empty(), + "200 response should have a body" + ); +} + +#[tokio::test] +async fn status_204_no_content() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http_no_redirect("/status_codes/status_204").await; + let status = page.status_code.as_u16(); + assert!( + status == 204 || status == 200, + "status_204 should return 204 or 200, got {}", + status + ); +} + +#[tokio::test] +async fn status_404_accessible() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http_no_redirect("/status_codes/status_404").await; + assert_eq!(page.status_code, StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn status_500_server_error() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http_no_redirect("/status_codes/status_500").await; + assert_eq!(page.status_code.as_u16(), 500); +} + +#[tokio::test] +async fn status_4xx_range() { + if !run_live_tests() { + return; + } + + let codes_4xx: &[u16] = &[ + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 420, 421, 422, 423, 424, 426, 428, 429, 431, 440, 444, 449, 450, 451, 494, 495, + 496, 497, 498, 499, + ]; + for &code in codes_4xx { + let path = format!("/status_codes/status_{}", code); + let page = fetch_page_http_no_redirect(&path).await; + let actual = page.status_code.as_u16(); + // Verify it's a client error or matches expected + assert!( + actual == code || (400..600).contains(&actual) || actual == 200, + "status_{}: expected {}xx range, got {}", + code, + code / 100, + actual + ); + } +} + +#[tokio::test] +async fn status_5xx_range() { + if !run_live_tests() { + return; + } + + let codes_5xx: &[u16] = &[ + 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 520, 598, 599, + ]; + for &code in codes_5xx { + let path = format!("/status_codes/status_{}", code); + let page = fetch_page_http_no_redirect(&path).await; + let actual = page.status_code.as_u16(); + assert!( + actual == code || (500..600).contains(&actual) || actual == 200, + "status_{}: expected 5xx range, got {}", + code, + actual + ); + } +} + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn status_codes_chrome_200() { + if !run_live_tests() { + return; + } + + if let Some(p) = fetch_page_chrome("/status_codes/status_200").await { + assert!(!p.get_html().is_empty(), "chrome 200 should have content"); + } else { + eprintln!("SKIP: chrome not available"); + } +} diff --git a/spider/tests/crawler_test_com/titles.rs b/spider/tests/crawler_test_com/titles.rs new file mode 100644 index 000000000..7fdf8c263 --- /dev/null +++ b/spider/tests/crawler_test_com/titles.rs @@ -0,0 +1,215 @@ +use crate::helpers::*; +use spider::reqwest::StatusCode; + +#[tokio::test] +async fn empty_title() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/empty_title").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let title = extract_title(&html); + assert!( + title.is_none() || title.as_ref().unwrap().is_empty(), + "empty_title should have no title text, got: {:?}", + title + ); +} + +#[tokio::test] +async fn missing_title() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/missing_title").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + !html.contains("") || !html.contains(""), + "missing_title should not have a title tag" + ); +} + +#[tokio::test] +async fn title_with_whitespace() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/title_with_whitespace").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let title = extract_title(&html); + assert!(title.is_some(), "should have a title tag"); +} + +#[tokio::test] +async fn title_over_max() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/title_over_max").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html(); + let title = extract_title(&html); + assert!(title.is_some(), "should have a title"); + let t = title.unwrap(); + assert!( + t.len() > 60, + "title_over_max should have a long title, got {} chars", + t.len() + ); +} + +#[tokio::test] +async fn title_warning() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/title_warning").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn page_title_length_n() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/page_title_length_n").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn page_title_width_n() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/page_title_width_n").await; + assert_eq!(page.status_code, StatusCode::OK); +} + +#[tokio::test] +async fn duplicate_title() { + if !run_live_tests() { + return; + } + + let paths = [ + "/titles/duplicate_title", + "/titles/duplicate_title/foo", + "/titles/duplicate_title/bar", + "/titles/duplicate_title/baz", + ]; + + let mut titles = Vec::new(); + for path in &paths { + let page = fetch_page_http(path).await; + assert_eq!( + page.status_code, + StatusCode::OK, + "{} should return 200", + path + ); + titles.push(extract_title(&page.get_html())); + } + + // All duplicate_title pages should share the same title + let first = &titles[0]; + for (i, title) in titles.iter().enumerate().skip(1) { + assert_eq!( + first, title, + "duplicate_title pages should share the same title: {} vs {}", + paths[0], paths[i] + ); + } +} + +#[tokio::test] +async fn duplicate_title_and_noindex() { + if !run_live_tests() { + return; + } + + let paths = [ + "/titles/duplicate_title_and_noindex/bat", + "/titles/duplicate_title_and_noindex/bak", + ]; + + for path in &paths { + let page = fetch_page_http(path).await; + assert_eq!( + page.status_code, + StatusCode::OK, + "{} should return 200", + path + ); + } +} + +#[tokio::test] +async fn svg_title_does_not_override_page_title() { + if !run_live_tests() { + return; + } + + let page = fetch_page_http("/titles/svg_title").await; + assert_eq!(page.status_code, StatusCode::OK); + let html = page.get_html().to_lowercase(); + assert!( + html.contains("(); + + let crawl = async move { + w.crawl_raw().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut urls = Vec::new(); + let sub = async { + loop { + spider::tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(page) = result { + urls.push(page.get_url().to_string()); + } else { + break; + } + } + } + } + }; + + let crawl_result = spider::tokio::time::timeout(std::time::Duration::from_secs(90), async { + spider::tokio::join!(sub, crawl) + }) + .await; + + assert!(crawl_result.is_ok(), "infinite crawl should not hang"); + + assert!( + urls.len() <= (budget as usize) + 2, + "budget should cap infinite crawl; got {} pages", + urls.len() + ); +} + +// --- Depth limit --- + +#[tokio::test] +async fn depth_limit_respected() { + if !run_live_tests() { + return; + } + + let pages = crawl_collect_http("/one/two/three/four", 10, 2).await; + assert!( + pages.len() <= 12, + "depth limit should constrain crawl; got {} pages", + pages.len() + ); +} + +// --- Chrome --- + +#[cfg(feature = "chrome")] +#[tokio::test] +async fn url_with_spaces_chrome() { + if !run_live_tests() { + return; + } + + if let Some(page) = fetch_page_chrome("/urls/url_with_spaces/URL%20with%20spaces").await { + assert!( + !page.get_html().is_empty(), + "chrome should handle URL with spaces" + ); + } else { + eprintln!("SKIP: chrome not available"); + } +} diff --git a/spider/tests/live_spider_agent_cloud.rs b/spider/tests/live_spider_agent_cloud.rs new file mode 100644 index 000000000..2f72e449c --- /dev/null +++ b/spider/tests/live_spider_agent_cloud.rs @@ -0,0 +1,124 @@ +#![cfg(feature = "agent")] + +use spider::agent::Agent; +use std::env; + +fn run_live_tests() -> bool { + matches!( + env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn summarize_response_usage(value: &serde_json::Value) -> String { + let first = if let Some(arr) = value.as_array() { + arr.first() + } else { + Some(value) + }; + + let Some(first) = first else { + return "empty-response".to_string(); + }; + + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + let status = first + .get("status") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + let duration_ms = first + .get("duration_elapsed_ms") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + format!( + "status={}, duration_ms={}, total_cost={}", + status, duration_ms, total_cost + ) +} + +#[tokio::test] +async fn live_spider_agent_cloud_search_and_scrape() -> Result<(), Box> { + if !run_live_tests() { + eprintln!("Skipping spider::agent live test (RUN_LIVE_TESTS not enabled)."); + return Ok(()); + } + + let api_key = env::var("SPIDER_CLOUD_API_KEY") + .ok() + .filter(|v| !v.trim().is_empty()) + .expect("RUN_LIVE_TESTS is enabled, but SPIDER_CLOUD_API_KEY is missing"); + + let agent = Agent::builder().with_spider_cloud(api_key).build()?; + + let search_body = serde_json::json!({ + "search": "books to scrape", + "num": 3, + "fetch_page_content": false + }) + .to_string(); + + let search_result = agent + .execute_custom_tool("spider_cloud_search", None, None, Some(&search_body)) + .await?; + assert!( + search_result.success, + "spider_cloud_search failed with HTTP {} body={}", + search_result.status, + search_result.body.chars().take(300).collect::() + ); + let search_json: serde_json::Value = serde_json::from_str(&search_result.body)?; + eprintln!( + "spider::agent search usage: {}", + summarize_response_usage(&search_json) + ); + + let scrape_body = serde_json::json!({ + "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", + "return_format": "markdown", + "limit": 1 + }) + .to_string(); + + let scrape_result = agent + .execute_custom_tool("spider_cloud_scrape", None, None, Some(&scrape_body)) + .await?; + assert!( + scrape_result.success, + "spider_cloud_scrape failed with HTTP {} body={}", + scrape_result.status, + scrape_result.body.chars().take(300).collect::() + ); + let scrape_json: serde_json::Value = serde_json::from_str(&scrape_result.body)?; + eprintln!( + "spider::agent scrape usage: {}", + summarize_response_usage(&scrape_json) + ); + + let usage = agent.usage(); + eprintln!( + "spider::agent usage snapshot: search_tool_calls={}, scrape_tool_calls={}, total_custom_tool_calls={}", + usage.get_custom_tool_calls("spider_cloud_search"), + usage.get_custom_tool_calls("spider_cloud_scrape"), + usage.total_custom_tool_calls(), + ); + assert_eq!(usage.get_custom_tool_calls("spider_cloud_search"), 1); + assert_eq!(usage.get_custom_tool_calls("spider_cloud_scrape"), 1); + + Ok(()) +} diff --git a/spider/tests/smart_vs_chrome.rs b/spider/tests/smart_vs_chrome.rs new file mode 100644 index 000000000..7e3675593 --- /dev/null +++ b/spider/tests/smart_vs_chrome.rs @@ -0,0 +1,296 @@ +//! Comparison test: crawl (chrome) vs crawl_smart on the same real-world URL. +//! +//! Verifies that smart mode produces HTML content comparable to full chrome +//! rendering on JS-heavy pages. +//! +//! Run: +//! RUN_LIVE_TESTS=1 cargo test --test smart_vs_chrome --features "smart" -- --nocapture + +#[cfg(all(feature = "chrome", feature = "smart"))] +mod compare { + use spider::tokio; + use spider::website::Website; + use std::time::Duration; + + const URL: &str = "https://fastbots.ai/blog"; + const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); + const CRAWL_TIMEOUT: Duration = Duration::from_secs(60); + + fn run_live_tests() -> bool { + matches!( + std::env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) + } + + fn build_website(url: &str) -> Website { + let mut w = Website::new(url); + w.with_limit(1) + .with_depth(0) + .with_request_timeout(Some(REQUEST_TIMEOUT)) + .with_crawl_timeout(Some(CRAWL_TIMEOUT)) + .with_respect_robots_txt(false); + w + } + + /// Crawl a URL via Chrome and collect the first page. + async fn fetch_chrome(url: &str) -> Option { + let website = build_website(url); + let mut w = website.clone(); + let mut rx = w.subscribe(4).expect("subscribe"); + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut page = None; + let sub = async { + loop { + tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(p) = result { + page = Some(p); + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + page + } + + /// Crawl a URL via smart mode and collect the first page. + async fn fetch_smart(url: &str) -> Option { + let website = build_website(url); + let mut w = website.clone(); + let mut rx = w.subscribe(4).expect("subscribe"); + let (done_tx, mut done_rx) = tokio::sync::oneshot::channel::<()>(); + + let crawl = async move { + w.crawl_smart().await; + w.unsubscribe(); + let _ = done_tx.send(()); + }; + + let mut page = None; + let sub = async { + loop { + tokio::select! { + biased; + _ = &mut done_rx => break, + result = rx.recv() => { + if let Ok(p) = result { + page = Some(p); + } else { + break; + } + } + } + } + }; + + tokio::join!(sub, crawl); + page + } + + /// Extract visible text tokens (lowercased, deduplicated, len > 3) after stripping tags. + fn text_tokens(html: &str) -> std::collections::HashSet { + let mut out = String::with_capacity(html.len()); + let mut in_tag = false; + for ch in html.chars() { + match ch { + '<' => in_tag = true, + '>' => { + in_tag = false; + out.push(' '); + } + _ if !in_tag => out.push(ch), + _ => {} + } + } + out.split_whitespace() + .filter(|w| w.len() > 3) + .map(|w| w.to_lowercase()) + .collect() + } + + #[tokio::test] + async fn fastbots_blog_crawl_chrome() { + if !run_live_tests() { + eprintln!("SKIP: set RUN_LIVE_TESTS=1 to run"); + return; + } + + let _ = env_logger::try_init(); + + let result = tokio::time::timeout(Duration::from_secs(90), fetch_chrome(URL)).await; + assert!(result.is_ok(), "crawl() should not timeout"); + + if let Some(page) = result.unwrap() { + let html = page.get_html(); + let status = page.status_code.as_u16(); + eprintln!("crawl() chrome: {} bytes, status={}", html.len(), status); + assert!( + html.len() > 1000, + "crawl() HTML too small: {} bytes (status={})", + html.len(), + status + ); + } else { + eprintln!("SKIP: crawl() returned no page (chrome unavailable)"); + } + } + + #[tokio::test] + async fn fastbots_blog_crawl_smart() { + if !run_live_tests() { + eprintln!("SKIP: set RUN_LIVE_TESTS=1 to run"); + return; + } + + let _ = env_logger::try_init(); + + let result = tokio::time::timeout(Duration::from_secs(90), fetch_smart(URL)).await; + assert!(result.is_ok(), "crawl_smart() should not timeout"); + + let page = result.unwrap(); + assert!( + page.is_some(), + "crawl_smart() should return at least one page" + ); + + let page = page.unwrap(); + let html = page.get_html(); + let status = page.status_code.as_u16(); + eprintln!("crawl_smart(): {} bytes, status={}", html.len(), status); + assert!( + html.len() > 1000, + "crawl_smart() HTML too small: {} bytes (status={})", + html.len(), + status + ); + } + + #[tokio::test] + async fn fastbots_blog_smart_matches_chrome() { + if !run_live_tests() { + eprintln!("SKIP: set RUN_LIVE_TESTS=1 to run"); + return; + } + + let _ = env_logger::try_init(); + + // --- Chrome rendering --- + eprintln!("Fetching via crawl() (chrome)..."); + let chrome_result = tokio::time::timeout(Duration::from_secs(90), fetch_chrome(URL)).await; + assert!(chrome_result.is_ok(), "crawl() should not timeout"); + + let chrome_page = chrome_result.unwrap(); + if chrome_page.is_none() { + eprintln!("SKIP: crawl() returned no page (chrome unavailable)"); + return; + } + let chrome_page = chrome_page.unwrap(); + let chrome_html = chrome_page.get_html(); + let chrome_len = chrome_html.len(); + + eprintln!( + "crawl() : {} bytes, status={}", + chrome_len, + chrome_page.status_code.as_u16() + ); + + if chrome_len == 0 { + eprintln!("SKIP: crawl() returned empty content"); + return; + } + + // --- Smart mode --- + eprintln!("Fetching via crawl_smart()..."); + let smart_result = tokio::time::timeout(Duration::from_secs(90), fetch_smart(URL)).await; + assert!(smart_result.is_ok(), "crawl_smart() should not timeout"); + + let smart_page = smart_result.unwrap(); + assert!( + smart_page.is_some(), + "crawl_smart() should return at least one page" + ); + let smart_page = smart_page.unwrap(); + let smart_html = smart_page.get_html(); + let smart_len = smart_html.len(); + + eprintln!( + "crawl_smart(): {} bytes, status={}", + smart_len, + smart_page.status_code.as_u16() + ); + + // --- Comparison --- + let chrome_tokens = text_tokens(&chrome_html); + let smart_tokens = text_tokens(&smart_html); + + let overlap = chrome_tokens.intersection(&smart_tokens).count(); + let chrome_token_count = chrome_tokens.len(); + let overlap_pct = if chrome_token_count > 0 { + (overlap as f64 / chrome_token_count as f64) * 100.0 + } else { + 0.0 + }; + + let size_ratio = if chrome_len > 0 { + smart_len as f64 / chrome_len as f64 + } else { + 0.0 + }; + + eprintln!("=== fastbots.ai/blog: crawl vs crawl_smart ==="); + eprintln!("Chrome: {} bytes | Smart: {} bytes", chrome_len, smart_len); + eprintln!("Size ratio (smart/chrome): {:.2}", size_ratio); + eprintln!( + "Token overlap: {}/{} ({:.1}%)", + overlap, chrome_token_count, overlap_pct + ); + + // Both should return substantial HTML content + assert!( + chrome_len > 1000, + "crawl() HTML too small: {} bytes", + chrome_len + ); + assert!( + smart_len > 1000, + "crawl_smart() HTML too small: {} bytes", + smart_len + ); + + // Smart should be at least 50% of chrome size + assert!( + size_ratio > 0.5, + "crawl_smart() content too small vs crawl(): {:.2}x ({} vs {} bytes)", + size_ratio, + smart_len, + chrome_len + ); + + // Text overlap: at least 50% of chrome tokens present in smart + assert!( + overlap_pct > 50.0, + "Text overlap too low: {:.1}% (expected >50%)", + overlap_pct + ); + + eprintln!("PASS: crawl_smart() content is comparable to crawl()"); + } +} diff --git a/spider_agent/BUILD.bazel b/spider_agent/BUILD.bazel new file mode 100644 index 000000000..546312c53 --- /dev/null +++ b/spider_agent/BUILD.bazel @@ -0,0 +1,59 @@ +""" +Spider Agent - A concurrent-safe multimodal agent for web automation and research. +""" + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "spider_agent", + srcs = glob(["src/**/*.rs"]), + edition = "2021", + deps = [ + "//spider_agent_types:spider_agent_types", + "//spider_agent_html:spider_agent_html", + "@crates_io//:tokio", + "@crates_io//:reqwest", + "@crates_io//:serde", + "@crates_io//:serde_json", + "@crates_io//:log", + "@crates_io//:thiserror", + "@crates_io//:dashmap", + "@crates_io//:parking_lot", + "@crates_io//:base64", + "@crates_io//:lol_html", + "@crates_io//:aho-corasick", + "@crates_io//:llm_models_spider", + ] + select({ + "//:openai_enabled": [ + "@crates_io//:async-openai", + "@crates_io//:tiktoken-rs", + ], + "//conditions:default": [], + }) + select({ + "//:chrome_enabled": [ + "@crates_io//:chromey", + "@crates_io//:futures", + ], + "//conditions:default": [], + }) + select({ + "//:webdriver_enabled": [ + "@crates_io//:thirtyfour", + ], + "//conditions:default": [], + }), + proc_macro_deps = [ + "@crates_io//:async-trait", + ], + rustc_flags = [] + select({ + "//:openai_enabled": ["--cfg", 'feature="openai"'], + "//conditions:default": [], + }) + select({ + "//:chrome_enabled": ["--cfg", 'feature="chrome"'], + "//conditions:default": [], + }) + select({ + "//:webdriver_enabled": ["--cfg", 'feature="webdriver"'], + "//conditions:default": [], + }), +) \ No newline at end of file diff --git a/spider_agent/Cargo.toml b/spider_agent/Cargo.toml new file mode 100644 index 000000000..b7fbf18c6 --- /dev/null +++ b/spider_agent/Cargo.toml @@ -0,0 +1,133 @@ +[package] +name = "spider_agent" +version = "2.47.22" +authors = ["j-mendez "] +description = "A concurrent-safe multimodal agent for web automation and research." +repository = "https://github.com/spider-rs/spider" +readme = "README.md" +keywords = ["agent", "llm", "automation", "web", "search"] +categories = ["web-programming", "asynchronous"] +license = "MIT" +documentation = "https://docs.rs/spider_agent" +edition = "2021" + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +# Core +tokio = { version = "1", features = ["sync", "rt"] } +async-trait = "0.1" +reqwest = { version = "0.13", features = ["json", "query"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +log = "0.4" +thiserror = "2" +dashmap = "6" +parking_lot = "0.12" +base64 = "0.22" + +# Extracted types and HTML processing +spider_agent_types = { version = "2.47.22", path = "../spider_agent_types" } +spider_agent_html = { version = "2.47.22", path = "../spider_agent_html" } + +# HTML processing (still needed for engine internals) +lol_html = "2" +aho-corasick = "1" + +# Model capabilities detection +llm_models_spider = "0.1.9" + +# LLM Providers (optional) +async-openai = { version = "0.33", optional = true, features = ["chat-completion"] } +tiktoken-rs = { version = "0.9", optional = true } + +# Browser (optional) - uses chromey (spider's chrome library) +chromey = { version = "2.40", optional = true, default-features = false, features = [ + "bytes", + "stream", +] } + +# WebDriver (optional) - uses thirtyfour +thirtyfour = { version = "0.36", optional = true } + +# Temp filesystem for large operations +tempfile = { version = "3", optional = true } + +# Stream utilities for event handling +futures = { version = "0.3", optional = true } + +# Long-term experience memory via semantic search +memvid-rs = { version = "1.2", optional = true } + +# Skill definitions (optional) +spider_skills = { version = "0.1.7", optional = true, default-features = false, features = ["web_challenges"] } + +# S3 skill loading (optional) +aws-sdk-s3 = { version = "1", optional = true } +aws-config = { version = "1", optional = true } + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } +env_logger = "0.11" +fastrand = "2" +futures = "0.3" +dotenvy = "0.15" + +[features] +default = [] + +# LLM Providers +openai = ["dep:async-openai", "dep:tiktoken-rs"] + +# Browser automation +chrome = ["dep:chromey", "dep:futures"] + +# WebDriver automation +webdriver = ["dep:thirtyfour"] + +# Temp filesystem support +fs = ["dep:tempfile"] + +# Skills - dynamic skill loading for web challenge solving +skills = ["dep:spider_skills"] + +# Long-term experience memory +memvid = ["dep:memvid-rs"] + +# S3 skill loading +skills_s3 = ["skills", "dep:aws-sdk-s3", "dep:aws-config"] + +# Search providers +search = [] +search_serper = ["search"] +search_brave = ["search"] +search_bing = ["search"] +search_tavily = ["search"] + +# Full feature set +full = ["openai", "chrome", "webdriver", "fs", "skills", "memvid", "search_serper", "search_brave", "search_bing", "search_tavily", "skills_s3"] + +[[example]] +name = "basic_search" +required-features = ["search_serper"] + +[[example]] +name = "research" +required-features = ["openai", "search_serper"] + +[[example]] +name = "extract" +required-features = ["openai"] + +[[example]] +name = "concurrent" +required-features = ["openai", "search_serper"] + +[[example]] +name = "multimodal" +required-features = ["openai", "search_serper"] + +[[example]] +name = "open_page_concurrent" +required-features = ["chrome"] diff --git a/spider_agent/README.md b/spider_agent/README.md new file mode 100644 index 000000000..f2e96c11b --- /dev/null +++ b/spider_agent/README.md @@ -0,0 +1,365 @@ +# Spider Agent + +A concurrent-safe multimodal agent for web automation and research. + +## Features + +- **Concurrent-safe**: Designed to be wrapped in `Arc` for multi-task access +- **Feature-gated**: Only include dependencies you need +- **Multiple LLM providers**: OpenAI, OpenAI-compatible APIs +- **Multiple search providers**: Serper, Brave, Bing, Tavily +- **HTML extraction**: Clean and extract structured data from web pages +- **Research synthesis**: Combine search + extraction + LLM synthesis + +### Advanced Automation Features + +- **Tool Calling Schema**: OpenAI-compatible function calling for reliable action parsing +- **HTML Diff Mode**: 50-70% token reduction by sending only page changes after first round +- **Planning Mode**: Multi-step planning reduces LLM round-trips +- **Parallel Synthesis**: Analyze N pages in a single LLM call +- **Confidence Tracking**: Smarter retry decisions based on LLM confidence scores +- **Self-Healing Selectors**: Auto-repair failed selectors with LLM diagnosis +- **Schema Generation**: Auto-generate JSON schemas from example outputs +- **Concurrent Chains**: Execute independent actions in parallel with dependency graphs + +## Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +spider_agent = { version = "0.1", features = ["openai", "search_serper"] } +``` + +## Quick Start + +```rust +use spider_agent::{Agent, AgentConfig}; +use std::sync::Arc; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let agent = Arc::new(Agent::builder() + .with_openai("sk-...", "gpt-4o-mini") + .with_search_serper("serper-key") + .build()?); + + // Search + let results = agent.search("rust web frameworks").await?; + println!("Found {} results", results.len()); + + // Extract from first result + let html = agent.fetch(&results.results[0].url).await?.html; + let data = agent.extract(&html, "Extract framework name and features").await?; + println!("{}", serde_json::to_string_pretty(&data)?); + + Ok(()) +} +``` + +## Concurrent Execution + +```rust +use spider_agent::Agent; +use std::sync::Arc; + +let agent = Arc::new(Agent::builder() + .with_openai("sk-...", "gpt-4o") + .with_search_serper("serper-key") + .with_max_concurrent_llm_calls(10) + .build()?); + +// Execute multiple searches concurrently +let queries = vec!["rust async", "rust web frameworks", "rust databases"]; +let mut handles = Vec::new(); + +for query in queries { + let agent = agent.clone(); + let query = query.to_string(); + handles.push(tokio::spawn(async move { + agent.search(&query).await + })); +} + +// Collect results +for handle in handles { + let result = handle.await??; + println!("Found {} results", result.results.len()); +} +``` + +## Research with Synthesis + +```rust +use spider_agent::{Agent, ResearchOptions}; + +let agent = Agent::builder() + .with_openai("sk-...", "gpt-4o") + .with_search_serper("serper-key") + .build()?; + +let research = agent.research( + "How do Tokio and async-std compare?", + ResearchOptions::new() + .with_max_pages(5) + .with_synthesize(true), +).await?; + +println!("Summary: {}", research.summary.unwrap()); +``` + +## Feature Flags + +| Feature | Description | +|---------|-------------| +| `openai` | OpenAI/OpenAI-compatible LLM provider | +| `chrome` | Browser automation via chromiumoxide | +| `search` | Base search functionality | +| `search_serper` | Serper.dev search provider | +| `search_brave` | Brave Search provider | +| `search_bing` | Bing Search provider | +| `search_tavily` | Tavily AI Search provider | +| `full` | All features | + +## Examples + +```bash +# Basic search +SERPER_API_KEY=xxx cargo run --example basic_search --features search_serper + +# Extract data +OPENAI_API_KEY=xxx cargo run --example extract --features openai + +# Research +OPENAI_API_KEY=xxx SERPER_API_KEY=xxx cargo run --example research --features "openai search_serper" + +# Concurrent execution +OPENAI_API_KEY=xxx SERPER_API_KEY=xxx cargo run --example concurrent --features "openai search_serper" +``` + +## Verification + +From the repository root: + +```bash +cargo check --workspace +cargo test -p spider_agent +cargo test -p spider_agent --features "openai search_serper" +RUN_LIVE_TESTS=1 cargo test -p spider_agent --features "openai search_serper" --test live_env_smoke -- --nocapture +``` + +## API Reference + +### Agent + +The main struct for all agent operations: + +- `search(query)` - Search the web +- `search_with_options(query, options)` - Search with custom options +- `fetch(url)` - Fetch a URL +- `extract(html, prompt)` - Extract data from HTML using LLM +- `extract_structured(html, schema)` - Extract data matching a JSON schema +- `research(topic, options)` - Research a topic with synthesis +- `prompt(messages)` - Send a prompt to the LLM +- `memory_get/set/clear()` - Session memory operations + +### AgentBuilder + +Configure and build agents: + +```rust +Agent::builder() + .with_config(config) + .with_system_prompt("You are a helpful assistant") + .with_max_concurrent_llm_calls(10) + .with_openai(api_key, model) + .with_spider_cloud("spider-cloud-api-key") + .with_search_serper(api_key) + .build() +``` + +### Spider Cloud Tool Inheritance + +You can register Spider Cloud routes as custom tools directly from the builder. + +```rust +use spider_agent::Agent; + +let agent = Agent::builder() + .with_spider_cloud("spider-cloud-api-key") + .build()?; + +// Available tools: +// - spider_cloud_crawl +// - spider_cloud_scrape +// - spider_cloud_search +// - spider_cloud_links +// - spider_cloud_transform +// - spider_cloud_unblocker +``` + +For full control (custom API URL, toggles, AI subscription gating), use `SpiderCloudToolConfig`: + +```rust +use spider_agent::{Agent, SpiderCloudToolConfig}; + +let spider_cloud = SpiderCloudToolConfig::new("spider-cloud-api-key") + .with_api_url("https://api.spider.cloud") + .with_tool_name_prefix("spider_cloud") + .with_enable_ai_routes(true); // Only enable if your plan includes /ai/* routes + +let agent = Agent::builder() + .with_spider_cloud_config(spider_cloud) + .build()?; +``` + +AI routes are disabled by default because they require a paid subscription: +https://spider.cloud/ai/pricing + +Prompt-driven route orchestration example: + +```bash +SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_prompt_flows \ + -- "run all flows for https://books.toscrape.com/ including search scrape crawl links transform unblocker" +``` + +To include AI routes (`/ai/crawl`, `/ai/scrape`, `/ai/search`, `/ai/browser`, `/ai/links`), enable both: +- config/env gate: `SPIDER_CLOUD_ENABLE_AI_ROUTES=1` +- prompt intent: include text like `include ai routes` + +End-to-end release example (single-prompt pipeline + report): + +```bash +SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_end_to_end \ + -- "Find top travel books on https://books.toscrape.com and return structured product fields" +``` + +Additional real-world examples: + +```bash +# E-commerce competitor intelligence +SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_ecommerce_competitor \ + -- "https://books.toscrape.com/" "travel books" + +# Job market intelligence pipeline +SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_jobs_pipeline \ + -- "rust engineer remote" "https://remoteok.com/remote-rust-jobs" +``` + +Notes: +- For markdown/text/raw/commonmark/bytes, use route-level `return_format`; transform is optional. +- For binary assets (PDF/images/files), `return_format: "bytes"` preserves fidelity. +- Run transform only when you explicitly need post-processing (`SPIDER_CLOUD_INCLUDE_TRANSFORM=1` in the examples). + +You can also point this at any compatible endpoint (not only `api.spider.cloud`) and +use your own naming convention: + +```rust +let spider_cloud = SpiderCloudToolConfig::new("provider-key") + .with_api_url("https://my-gateway.example.com/v1") + .with_tool_name_prefix("web_api"); // tools become web_api_search, web_api_scrape, etc. +``` + +## Advanced Configuration + +### RemoteMultimodalConfig + +Configure automation features. Use preset configurations for optimal performance: + +```rust +use spider_agent::RemoteMultimodalConfig; + +// Fast mode: All performance-positive features enabled +// - Tool calling (Auto), HTML diff (Auto), Confidence retries, Concurrent execution +let config = RemoteMultimodalConfig::fast(); + +// Fast with planning: Adds multi-step planning and self-healing +// Best for complex multi-step automations +let config = RemoteMultimodalConfig::fast_with_planning(); + +// Manual configuration for fine-grained control: +use spider_agent::{ + ToolCallingMode, HtmlDiffMode, ReasoningEffort, + PlanningModeConfig, SelfHealingConfig, ConfidenceRetryStrategy, +}; + +let config = RemoteMultimodalConfig::default() + .with_tool_calling_mode(ToolCallingMode::Auto) + .with_html_diff_mode(HtmlDiffMode::Auto) + .with_reasoning_effort(Some(ReasoningEffort::Medium)) + .with_planning_mode(PlanningModeConfig::default()) + .with_self_healing(SelfHealingConfig::default()) + .with_confidence_strategy(ConfidenceRetryStrategy::default()) + .with_concurrent_execution(true); +``` + +`reasoning_effort` is optional and only sent when configured, so OpenAI-compatible providers that do not support reasoning controls remain unaffected. + +### Concurrent Action Chains + +Execute independent actions in parallel using dependency graphs: + +```rust +use spider_agent::{DependentStep, DependencyGraph, ConcurrentChainConfig, execute_graph}; + +// Define steps with dependencies +let steps = vec![ + DependentStep::new("fetch_data", json!({"Navigate": "https://example.com"})), + DependentStep::new("click_a", json!({"Click": "#btn-a"})).depends_on("fetch_data"), + DependentStep::new("click_b", json!({"Click": "#btn-b"})).depends_on("fetch_data"), + DependentStep::new("submit", json!({"Click": "#submit"})) + .depends_on("click_a") + .depends_on("click_b"), +]; + +// Create dependency graph +let mut graph = DependencyGraph::new(steps)?; + +// Execute with parallel-safe actions running concurrently +let config = ConcurrentChainConfig::default(); +let result = execute_graph(&mut graph, &config, |step| async move { + // Your execution logic here + StepResult::success() +}).await; +``` + +### Schema Generation + +Auto-generate JSON schemas from examples: + +```rust +use spider_agent::{generate_schema, SchemaGenerationRequest}; + +let request = SchemaGenerationRequest { + examples: vec![ + json!({"name": "Product A", "price": 19.99}), + json!({"name": "Product B", "price": 29.99}), + ], + description: Some("Product listing data".to_string()), + strict: false, + name: Some("products".to_string()), +}; + +let schema = generate_schema(&request); +// Use schema.to_extraction_schema() for structured extraction +``` + +### Performance Features + +| Feature | Default | `fast()` | Impact | +|---------|---------|----------|--------| +| Tool Calling | `JsonObject` | `Auto` | ~30% reduction in parse errors | +| HTML Diff | `Disabled` | `Auto` | 50-70% token reduction | +| Planning Mode | `None` | `None` | Fewer LLM round-trips | +| Parallel Synthesis | `None` | `None` | N pages = 1 LLM call | +| Confidence | `None` | `Enabled` | Smarter retry decisions | +| Self-Healing | `None` | `None` | Higher success rate on failures | +| Concurrent Execution | `false` | `true` | Parallel action execution | + +**Recommended**: Use `RemoteMultimodalConfig::fast()` for optimal performance. + +All features are opt-in with zero overhead when disabled. + +## License + +MIT diff --git a/spider_agent/SKILLS.md b/spider_agent/SKILLS.md new file mode 100644 index 000000000..c462fa57e --- /dev/null +++ b/spider_agent/SKILLS.md @@ -0,0 +1,190 @@ +# Skills Integration (spider_agent) + +How `spider_agent` consumes and extends skills from the `spider_skills` crate. + +## Architecture + +``` +spider_skills (v0.1.7) spider_agent +┌─────────────────────┐ ┌──────────────────────────────┐ +│ 110 skills │ │ automation/skills.rs │ +│ (.md via include_str)│──import──▶│ builtin_web_challenges() │ +│ triggers + content │ │ + pre_evaluate JS overlays │ +│ web_challenges.rs │ │ │ +└─────────────────────┘ │ automation/browser.rs │ + │ per-round skill injection │ + │ pre_evaluate execution │ + │ engine game loops │ + └──────────────────────────────┘ +``` + +`spider_agent` does NOT define any skill content — all prompt text lives in `spider_skills`. The agent only adds: +1. **Pre-evaluate JS** — Browser-side computation that runs before LLM inference +2. **Engine game loops** — Tight CDP loops for time-sensitive challenges (TTT, whack-a-mole, word search drag, nested grid click, draw circle) +3. **Skill injection** — Per-round matching and context injection into the LLM prompt + +## Feature Flags + +| Flag | Crate | Enables | +|------|-------|---------| +| `skills` | spider_agent | `spider_skills/web_challenges` dependency | +| `skills_s3` | spider_agent | S3 skill loading (`skills` + `aws-sdk-s3`) | +| `agent_skills` | spider | `agent` + `spider_agent/skills` | +| `agent_skills_s3` | spider | `agent_skills` + `spider_agent/skills_s3` | +| `agent_full` | spider | Everything including skills | + +```toml +# In spider/Cargo.toml — enable skills +spider = { version = "2", features = ["agent_skills"] } + +# Or with S3 loading +spider = { version = "2", features = ["agent_skills_s3"] } +``` + +## Pre-Evaluate JS Overlays + +`builtin_web_challenges()` in `automation/skills.rs` loads all 110 skills from `spider_skills::web_challenges::registry()`, then patches 8 skills with engine-specific `pre_evaluate` JavaScript: + +| Skill | JS Prefix | What It Does | +|-------|-----------|-------------| +| `rotation-puzzle` | `ROT:` | Reads CSS transform matrix per tile, computes clicks needed | +| `tic-tac-toe` | `TTT:` | Reads board state, computes optimal move (minimax), auto-clicks via dispatchEvent | +| `word-search` | `WS:` / `WS_DRAG:` | Extracts letter grid, finds all words algorithmically, outputs drag coordinates | +| `nested-grid` | `NEST:` | Detects stop sign image, computes bounding-rect overlap with leaf boxes | +| `whack-a-mole` | `WAM:` | Detects visible moles, auto-clicks them via dispatchEvent | +| `draw-circle` | `CIRCLE:` | Finds canvas/draw area, computes circular path coordinates | +| `sliding-puzzle` | `SLIDE:` | Reads tile positions and classes for puzzle solver | +| `math-solver` | `MATH:` | Extracts math expression from page, evaluates answer | + +### How Pre-Evaluate Works + +Each round, before the LLM sees the page: + +1. `find_pre_evaluates(url, title, html)` returns matching skills with JS payloads +2. Engine runs each JS via `page.evaluate()` in the browser +3. JS writes structured results to `document.title` (e.g., `TTT:{"board":"M..T.....","best":4}`) +4. LLM reads the modified title and emits simple actions (Click, Wait) instead of writing extraction code + +``` +Browser Page ──▶ pre_evaluate JS ──▶ Modified Title ──▶ LLM reads title ──▶ Click actions +``` + +This eliminates the model's tendency to write incorrect DOM selectors or extraction code. + +### Title Prefixes + +| Prefix | Skill | Data Format | +|--------|-------|-------------| +| `ROT:{...}` | rotation-puzzle | `{n, done, clicks:"idx:count,..."}` | +| `TTT:{...}` | tic-tac-toe | `{n, board, best, clicked, myWin, thWin, full}` | +| `WS:{...}` | word-search | `{n, rows, cols, grid, words, found}` | +| `WS_DRAG:{...}` | word-search | `{n, words, drags}` (engine auto-drags) | +| `NEST:{...}` | nested-grid | `{total, sel, toClick, selected, hasSign, boxes}` | +| `WAM:{...}` | whack-a-mole | `{moles, clicked}` | +| `CIRCLE:{...}` | draw-circle | `{cx, cy, rad, pts}` | +| `SLIDE:{...}` | sliding-puzzle | `{n, tiles:[{id,x,y,w,h,txt,cls}]}` | +| `MATH:{...}` | math-solver | `{expr, answer}` | +| `*_ERR:msg` | any | Error during pre_evaluate | + +## Engine Game Loops + +For time-sensitive challenges, the engine runs tight loops via CDP without waiting for LLM rounds: + +### Tic-Tac-Toe Loop +- Plays entire game in ~0.7s/move tight loop +- Up to 3 games (handles draws by waiting for auto-reset) +- Clicks verify button on win +- Uses `dispatchEvent` for clicks (works on this specific page) + +### Word Search CDP Drag +- Engine dispatches `MousePressed` → `MouseMoved` → `MouseReleased` through cell centers +- `buttons: 1` bitmask required during drag moves +- DOM marker `#ws-engine-done` prevents re-drag + +### Nested Grid Auto-Click +- Computes bounding-rect overlap between stop sign image and leaf boxes +- Auto-clicks matching boxes via `ClickPoint` +- Handles recursive subdivision (clicking splits boxes into smaller grids) + +### Whack-a-Mole Loop +- Detects visible moles via class/src/style checks +- Auto-clicks via `dispatchEvent` (pointerdown → mousedown → pointerup → mouseup → click) +- Runs every pre_evaluate round + +### Draw Circle Path +- Computes 36-point circular path around canvas center +- Engine dispatches CDP mouse events along the path + +## Per-Round Skill Injection + +In `automation/browser.rs` (~line 709), each inference round: + +1. **Match skills** — `registry.match_context_limited(url, title, html, 3, 4000)` returns combined prompt for up to 3 matching skills within 4000 chars +2. **Run pre_evaluate** — For matched skills with `pre_evaluate`, execute JS in browser +3. **Inject context** — Matched skill content is appended to `system_prompt_extra` for that round +4. **Stagnation check** — Raw title (before pre_evaluate) used for stuck detection so pre_evaluate doesn't mask stagnation + +### Context Budget + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `max_skills` | 3 | Maximum skills injected per round | +| `max_chars` | 4000 | Maximum total chars for skill context | + +Skills are sorted by priority (descending). If adding the next skill would exceed `max_chars`, it's skipped. + +## Agent-Driven Skill Loading + +When no skills auto-match, the agent can request skills via the `request_skill` memory operation. A skill catalog is injected into the prompt listing available skill names. The model can then request a specific skill by name. + +## S3 Skill Loading + +Load custom skills from S3-compatible storage (AWS S3, MinIO, Cloudflare R2): + +```rust +use spider_agent::automation::skills::{S3SkillSource, load_from_s3, builtin_web_challenges}; + +let mut registry = builtin_web_challenges(); +let source = S3SkillSource::new("my-bucket", "skills/") + .with_region("us-east-1") + .with_endpoint_url("https://r2.example.com"); // optional, for R2/MinIO + +let loaded = load_from_s3(&mut registry, &source).await?; +// S3 skills override built-in skills with the same name +``` + +Or combine in one call: + +```rust +use spider_agent::automation::skills::{S3SkillSource, with_builtin_and_s3}; + +let source = S3SkillSource::new("my-bucket", "skills/"); +let registry = with_builtin_and_s3(&source).await?; +``` + +## Adding Custom Pre-Evaluate JS + +To add engine-side computation for a custom or existing skill: + +```rust +let mut registry = builtin_web_challenges(); + +if let Some(skill) = registry.get_mut("my-skill") { + skill.pre_evaluate = Some(r#" + try { + // Extract state from the page + const data = /* ... */; + document.title = 'MYPREFIX:' + JSON.stringify(data); + } catch(e) { + document.title = 'MYPREFIX_ERR:' + e.message; + } + "#.to_string()); +} +``` + +Guidelines for pre_evaluate JS: +- Always wrap in `try/catch`, write errors to title with `_ERR:` suffix +- Write results to `document.title` with a unique prefix +- Keep JS compact — it runs every matching round +- Use `dispatchEvent` only when the target page handles custom events (rare). Prefer having the LLM emit real Click/ClickPoint actions. +- Check for a "done" marker element to short-circuit repeated runs: `if(document.getElementById('my-done')){...}` diff --git a/spider_agent/examples/basic_search.rs b/spider_agent/examples/basic_search.rs new file mode 100644 index 000000000..c6451acd8 --- /dev/null +++ b/spider_agent/examples/basic_search.rs @@ -0,0 +1,50 @@ +//! Basic search example using spider_agent. +//! +//! Run with: +//! ```sh +//! SERPER_API_KEY=your-key cargo run --example basic_search --features search_serper +//! ``` + +use spider_agent::{Agent, SearchOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = + std::env::var("SERPER_API_KEY").expect("SERPER_API_KEY environment variable required"); + + let agent = Agent::builder().with_search_serper(api_key).build()?; + + // Basic search + println!("Searching for 'rust web frameworks'...\n"); + let results = agent.search("rust web frameworks").await?; + + println!("Found {} results:\n", results.len()); + for result in &results.results { + println!("{}. {}", result.position, result.title); + println!(" URL: {}", result.url); + if let Some(ref snippet) = result.snippet { + println!(" {}", snippet); + } + println!(); + } + + // Search with options + println!("\nSearching with limit of 3 results...\n"); + let results = agent + .search_with_options( + "rust async runtime comparison", + SearchOptions::new() + .with_limit(3) + .with_country("us") + .with_language("en"), + ) + .await?; + + for result in &results.results { + println!("- {} ({})", result.title, result.url); + } + + Ok(()) +} diff --git a/spider_agent/examples/concurrent.rs b/spider_agent/examples/concurrent.rs new file mode 100644 index 000000000..5d1ba7c47 --- /dev/null +++ b/spider_agent/examples/concurrent.rs @@ -0,0 +1,132 @@ +//! Concurrent execution example using spider_agent. +//! +//! Run with: +//! ```sh +//! OPENAI_API_KEY=your-key SERPER_API_KEY=your-key cargo run --example concurrent --features "openai search_serper" +//! ``` + +use spider_agent::Agent; +use std::sync::Arc; +use std::time::Instant; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let openai_key = + std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY environment variable required"); + let serper_key = + std::env::var("SERPER_API_KEY").expect("SERPER_API_KEY environment variable required"); + + // Create agent with Arc for sharing across tasks + let agent = Arc::new( + Agent::builder() + .with_openai(openai_key, "gpt-4o-mini") + .with_search_serper(serper_key) + .with_max_concurrent_llm_calls(10) + .build()?, + ); + + let queries = vec![ + "rust async programming", + "rust web frameworks comparison", + "rust database libraries", + "rust error handling best practices", + "rust testing strategies", + ]; + + println!("Executing {} searches concurrently...\n", queries.len()); + let start = Instant::now(); + + // Spawn concurrent search tasks + let mut handles = Vec::new(); + + for query in queries { + let agent = agent.clone(); + let query = query.to_string(); + + handles.push(tokio::spawn(async move { + let result = agent.search(&query).await; + (query, result) + })); + } + + // Collect results + let mut results = Vec::new(); + for handle in handles { + results.push(handle.await?); + } + + let elapsed = start.elapsed(); + + // Print results + for (query, result) in results { + match result { + Ok(search_results) => { + println!("'{}' -> {} results", query, search_results.len()); + if let Some(first) = search_results.results.first() { + println!(" Top result: {}", first.title); + } + } + Err(e) => { + println!("'{}' -> Error: {}", query, e); + } + } + } + + println!("\nCompleted in {:?}", elapsed); + + // Now do concurrent extraction + println!("\n---\n"); + println!("Fetching and extracting from multiple URLs concurrently...\n"); + + let urls = vec!["https://example.com", "https://httpbin.org/html"]; + + let start = Instant::now(); + let mut handles = Vec::new(); + + for url in urls { + let agent = agent.clone(); + let url = url.to_string(); + + handles.push(tokio::spawn(async move { + // Fetch + let fetch_result = agent.fetch(&url).await?; + + // Extract (this will use the LLM semaphore) + let extracted = agent + .extract( + &fetch_result.html, + "Extract the main content and any headings", + ) + .await?; + + Ok::<_, spider_agent::AgentError>((url, extracted)) + })); + } + + // Collect results + for handle in handles { + match handle.await? { + Ok((url, data)) => { + println!("Extracted from {}:", url); + println!( + "{}", + serde_json::to_string_pretty(&data) + .unwrap_or_default() + .chars() + .take(300) + .collect::() + ); + println!(); + } + Err(e) => { + println!("Error: {}", e); + } + } + } + + println!("Extraction completed in {:?}", start.elapsed()); + + Ok(()) +} diff --git a/spider_agent/examples/extract.rs b/spider_agent/examples/extract.rs new file mode 100644 index 000000000..149e37246 --- /dev/null +++ b/spider_agent/examples/extract.rs @@ -0,0 +1,65 @@ +//! Extraction example using spider_agent. +//! +//! Run with: +//! ```sh +//! OPENAI_API_KEY=your-key cargo run --example extract --features openai +//! ``` + +use spider_agent::Agent; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = + std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY environment variable required"); + + let agent = Agent::builder() + .with_openai(api_key, "gpt-4o-mini") + .build()?; + + // Fetch a page + println!("Fetching https://example.com...\n"); + let result = agent.fetch("https://example.com").await?; + println!("Status: {}", result.status); + println!("Content-Type: {}", result.content_type); + println!("HTML length: {} bytes\n", result.html.len()); + + // Extract data + println!("Extracting data...\n"); + let data = agent + .extract( + &result.html, + "Extract the main heading and any links on the page. Return as JSON with 'heading' and 'links' fields.", + ) + .await?; + + println!("Extracted data:"); + println!("{}", serde_json::to_string_pretty(&data)?); + + // Extract with schema + println!("\nExtracting with schema...\n"); + let schema = serde_json::json!({ + "type": "object", + "properties": { + "title": { "type": "string", "description": "Page title or main heading" }, + "description": { "type": "string", "description": "Page description or summary" }, + "links": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { "type": "string" }, + "url": { "type": "string" } + } + } + } + } + }); + + let structured_data = agent.extract_structured(&result.html, &schema).await?; + println!("Structured data:"); + println!("{}", serde_json::to_string_pretty(&structured_data)?); + + Ok(()) +} diff --git a/spider_agent/examples/extraction.rs b/spider_agent/examples/extraction.rs new file mode 100644 index 000000000..11907ca6c --- /dev/null +++ b/spider_agent/examples/extraction.rs @@ -0,0 +1,100 @@ +//! Standalone extraction example using spider_agent's RemoteMultimodalEngine. +//! +//! This example demonstrates extracting structured data from HTML using +//! spider_agent without requiring the full spider crate or Chrome. +//! +//! Run with: +//! ```bash +//! OPEN_ROUTER=your-api-key cargo run --example extraction -p spider_agent +//! ``` + +use spider_agent::automation::RemoteMultimodalEngine; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize logging + env_logger::init(); + + // Get API key from environment variable + let api_key = + std::env::var("OPEN_ROUTER").expect("OPEN_ROUTER environment variable must be set"); + + // Create the engine with OpenRouter + let mut engine = RemoteMultimodalEngine::new( + "https://openrouter.ai/api/v1/chat/completions", + "qwen/qwen-2-vl-72b-instruct", + None, // Use default system prompt + ) + .with_api_key(Some(&api_key)); + + // Configure extraction settings + engine.cfg.extra_ai_data = true; + engine.cfg.include_html = true; + engine.cfg.request_json_object = true; + engine.cfg.max_tokens = 1024; + + // Set custom extraction instructions + engine.user_message_extra = Some( + "Extract the book details including: title, price, availability, description, and UPC code." + .to_string(), + ); + + // Sample HTML (simulating fetched content) + let html = r#" + + + A Light in the Attic | Books to Scrape + +
+

A Light in the Attic

+

£51.77

+

In stock (22 available)

+ + + + + + + +
UPCa897fe39b1053632
Product TypeBooks
Price (excl. tax)£51.77
Price (incl. tax)£51.77
Tax£0.00
Number of reviews0
+
+
+

It's hard to imagine a world without A Light in the Attic. + This now-classic collection of poetry and drawings from Shel Silverstein + celebrates its 20th anniversary with this special edition.

+
+ + + "#; + + println!("=== Spider Agent Extraction Example ===\n"); + + // Extract data from HTML + let start = std::time::Instant::now(); + let result = engine + .extract_from_html( + html, + "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", + Some("A Light in the Attic | Books to Scrape"), + ) + .await?; + let duration = start.elapsed(); + + println!("Label: {}", result.label); + println!("Success: {}", result.success); + + if let Some(extracted) = &result.extracted { + println!("\nExtracted Data:"); + println!("{}", serde_json::to_string_pretty(extracted)?); + } + + println!("\nUsage:"); + println!(" Prompt tokens: {}", result.usage.prompt_tokens); + println!(" Completion tokens: {}", result.usage.completion_tokens); + println!(" Total tokens: {}", result.usage.total_tokens); + println!(" LLM calls: {}", result.usage.llm_calls); + + println!("\nTime elapsed: {:?}", duration); + + Ok(()) +} diff --git a/spider_agent/examples/multimodal.rs b/spider_agent/examples/multimodal.rs new file mode 100644 index 000000000..4c2eeb9b9 --- /dev/null +++ b/spider_agent/examples/multimodal.rs @@ -0,0 +1,181 @@ +//! Multimodal agent example with usage tracking. +//! +//! This example demonstrates: +//! - Concurrent search and extraction +//! - Usage statistics tracking (tokens, calls) +//! - Memory for storing session state +//! +//! Run with: +//! ```sh +//! OPENAI_API_KEY=your-key SERPER_API_KEY=your-key cargo run --example multimodal --features "openai search_serper" +//! ``` + +use spider_agent::{Agent, SearchOptions}; +use std::sync::Arc; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let openai_key = + std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY environment variable required"); + let serper_key = + std::env::var("SERPER_API_KEY").expect("SERPER_API_KEY environment variable required"); + + // Create agent with Arc for concurrent access + let agent = Arc::new( + Agent::builder() + .with_openai(&openai_key, "gpt-4o-mini") + .with_search_serper(&serper_key) + .with_max_concurrent_llm_calls(5) + .build()?, + ); + + println!("=== Multimodal Agent Example ===\n"); + + // Store query in memory for session tracking + agent.memory_set( + "session_id", + serde_json::json!(uuid::Uuid::new_v4().to_string()), + ); + agent.memory_set("queries_processed", serde_json::json!(0)); + + // Concurrent searches + let queries = vec![ + "Rust async runtime comparison 2024", + "Best Rust web frameworks", + "Rust database libraries", + ]; + + println!("Running {} concurrent searches...\n", queries.len()); + + let mut handles = Vec::new(); + for query in queries.clone() { + let agent = agent.clone(); + let query = query.to_string(); + handles.push(tokio::spawn(async move { + let opts = SearchOptions::new().with_limit(3); + let result = agent.search_with_options(&query, opts).await; + (query, result) + })); + } + + // Collect results + let mut all_urls = Vec::new(); + for handle in handles { + let (query, result) = handle.await?; + match result { + Ok(results) => { + println!("'{}': {} results", query, results.len()); + for r in results.results.iter().take(2) { + all_urls.push(r.url.clone()); + } + } + Err(e) => println!("'{}': Error - {}", query, e), + } + } + + // Update memory + let count = agent + .memory_get("queries_processed") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + agent.memory_set( + "queries_processed", + serde_json::json!(count + queries.len() as u64), + ); + + // Extract from a few pages concurrently + println!( + "\n=== Extracting from {} pages ===\n", + all_urls.len().min(3) + ); + + let mut extract_handles = Vec::new(); + for url in all_urls.into_iter().take(3) { + let agent = agent.clone(); + extract_handles.push(tokio::spawn(async move { + match agent.fetch(&url).await { + Ok(fetch) => { + let extraction = agent + .extract( + &fetch.html, + "Extract: title, main topic, key points (max 3)", + ) + .await; + (url, Some(fetch.status), extraction.ok()) + } + Err(_) => (url, None, None), + } + })); + } + + for handle in extract_handles { + let (url, status, extraction) = handle.await?; + println!("URL: {}", url); + if let Some(status) = status { + println!(" Status: {}", status); + } + if let Some(data) = extraction { + println!( + " Extracted: {}", + serde_json::to_string(&data) + .unwrap_or_default() + .chars() + .take(150) + .collect::() + ); + } + println!(); + } + + // Print usage statistics + println!("=== Usage Statistics ===\n"); + let usage = agent.usage(); + println!("LLM Calls: {}", usage.llm_calls); + println!("Prompt Tokens: {}", usage.prompt_tokens); + println!("Completion Tokens:{}", usage.completion_tokens); + println!("Total Tokens: {}", usage.total_tokens()); + println!("Search Calls: {}", usage.search_calls); + println!("Fetch Calls: {}", usage.fetch_calls); + println!("Tool Calls: {}", usage.tool_calls); + + // Print memory state + println!("\n=== Session Memory ===\n"); + if let Some(session) = agent.memory_get("session_id") { + println!("Session ID: {}", session); + } + if let Some(count) = agent.memory_get("queries_processed") { + println!("Queries Processed: {}", count); + } + + Ok(()) +} + +// Simple UUID generation (in real code, use uuid crate) +mod uuid { + pub struct Uuid([u8; 16]); + + impl Uuid { + pub fn new_v4() -> Self { + let mut bytes = [0u8; 16]; + for b in &mut bytes { + *b = fastrand::u8(..); + } + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + Self(bytes) + } + + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + format!( + "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + self.0[0], self.0[1], self.0[2], self.0[3], + self.0[4], self.0[5], self.0[6], self.0[7], + self.0[8], self.0[9], self.0[10], self.0[11], + self.0[12], self.0[13], self.0[14], self.0[15] + ) + } + } +} diff --git a/spider_agent/examples/open_page_concurrent.rs b/spider_agent/examples/open_page_concurrent.rs new file mode 100644 index 000000000..90af2764b --- /dev/null +++ b/spider_agent/examples/open_page_concurrent.rs @@ -0,0 +1,286 @@ +//! OpenPage concurrent spawning example. +//! +//! This example demonstrates the OpenPage action that spawns new browser pages +//! concurrently when the user asks to "go to" or "open" a new URL (rather than +//! clicking a link on the current page). +//! +//! Run with: +//! ```sh +//! OPEN_ROUTER=sk-or-your-key cargo run --example open_page_concurrent --features chrome +//! ``` +//! +//! Or with OpenAI: +//! ```sh +//! OPENAI_API_KEY=your-key cargo run --example open_page_concurrent --features chrome +//! ``` + +use chromiumoxide::{Browser, BrowserConfig}; +use futures::StreamExt; +use spider_agent::automation::{RemoteMultimodalConfig, RemoteMultimodalConfigs}; +use spider_agent::{ + run_remote_multimodal_with_page, run_spawn_pages_with_options, SpawnPageOptions, +}; +use std::sync::Arc; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + // Get API configuration from environment + let (api_url, api_key, model_name) = get_api_config()?; + + println!("=== OpenPage Concurrent Spawning Example ===\n"); + println!("API URL: {}", api_url); + println!("Model: {}\n", model_name); + + // Launch browser + println!("Launching browser..."); + let (browser, mut handler) = Browser::launch(BrowserConfig::builder().build()?).await?; + + // Spawn browser handler task + let handle = tokio::spawn(async move { + while let Some(h) = handler.next().await { + if h.is_err() { + break; + } + } + }); + + let browser = Arc::new(browser); + + // Create a new page + let page = browser.new_page("about:blank").await?; + + // Stay on blank page - don't navigate to example.com + println!("Using blank page as starting point..."); + // page is already at about:blank from browser.new_page() + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + + // Configure the remote multimodal engine + let config = RemoteMultimodalConfigs::new(&api_url, &model_name) + .with_api_key(&api_key) + .with_cfg( + RemoteMultimodalConfig::new() + .with_max_rounds(2) + .with_screenshot(true), + ) + .with_system_prompt_extra( + r#" +CRITICAL INSTRUCTION FOR THIS SESSION: +The user wants to open multiple URLs in parallel browser tabs. +When the user asks to "open", "go to", or "visit" multiple URLs, you MUST use the OpenPage action: +- Single URL: { "OpenPage": "https://example.com" } +- Multiple URLs: { "OpenPage": ["https://url1.com", "https://url2.com"] } + +OpenPage spawns NEW browser tabs that run concurrently. This is different from Navigate which replaces the current page. +"#, + ) + .with_user_message_extra( + r#" +IGNORE THE CURRENT PAGE CONTENT. Do NOT click any links on the current page. + +YOUR ONLY TASK: Use the OpenPage action to open these URLs in new browser tabs: +- https://httpbin.org/html +- https://www.rust-lang.org + +REQUIRED OUTPUT FORMAT - copy this exactly but with done: false: +{ + "label": "Opening URLs in new tabs", + "done": false, + "steps": [ + { "OpenPage": ["https://httpbin.org/html", "https://www.rust-lang.org"] } + ] +} + +Do NOT use Click, Navigate, or any other action. ONLY use OpenPage. +"#, + ); + + // Run automation + println!("Running automation with OpenPage instruction...\n"); + + // Enable RUST_LOG=debug to see what actions the model returns + let result = run_remote_multimodal_with_page(&config, &page, "about:blank").await?; + + println!("Automation Result:"); + println!( + " Full result: {:?}", + serde_json::to_string_pretty(&serde_json::json!({ + "label": &result.label, + "steps_executed": result.steps_executed, + "success": result.success, + "spawn_pages": &result.spawn_pages, + "extracted": &result.extracted, + })) + .unwrap_or_default() + ); + println!(" Label: {}", result.label); + println!(" Steps executed: {}", result.steps_executed); + println!(" Success: {}", result.success); + + // Check for spawn_pages + if result.has_spawn_pages() { + println!("\n URLs to spawn in new pages:"); + for url in &result.spawn_pages { + println!(" - {}", url); + } + + // Spawn concurrent pages with extraction and screenshots enabled + // This runs all pages in parallel using tokio::spawn internally + println!("\nSpawning concurrent pages with extraction...\n"); + + // Setup a page setup callback for event tracking propagation + // In a real spider integration, this would setup ChromeEventTracker + let page_setup = std::sync::Arc::new(Box::new(|_page: &chromiumoxide::Page| { + Box::pin(async move { + // Example: Setup event tracking on this spawned page + // In spider, you would call setup_chrome_events(page, tracker) here + // to propagate the network map from the main page + log::debug!("Setting up event tracking on spawned page"); + }) as std::pin::Pin + Send>> + }) as spider_agent::PageSetupFn); + + // Configure what we want from each page + let options = SpawnPageOptions::new() + .with_extraction( + "Extract the page title, main heading, and a brief description of the page content", + ) + .with_screenshot(true) + .with_max_rounds(1) + .with_page_setup(page_setup) + .with_track_bytes(true); + + let spawn_results = + run_spawn_pages_with_options(&browser, result.spawn_pages, &config, options).await; + + // Process results - use the convenient accessor methods + let mut successes = 0; + let mut failures = 0; + let mut total_tokens = 0u32; + let mut total_bytes = 0.0f64; + let mut total_requests = 0usize; + + for spawn_result in &spawn_results { + if spawn_result.is_ok() { + println!("Page '{}' completed:", spawn_result.url); + + // Get label + if let Some(label) = spawn_result.label() { + println!(" Label: {}", label); + } + + // Get extracted data + if let Some(extracted) = spawn_result.extracted() { + println!( + " Extracted: {}", + serde_json::to_string(extracted) + .unwrap_or_default() + .chars() + .take(300) + .collect::() + ); + } + + // Check for screenshot + if let Some(screenshot) = spawn_result.screenshot() { + println!(" Screenshot: {} bytes (base64)", screenshot.len()); + } + + // Show bytes transferred from network events + if let Some(bytes) = spawn_result.bytes_transferred { + println!(" Bytes transferred: {:.0}", bytes); + total_bytes += bytes; + } + if let Some(ref response_map) = spawn_result.response_map { + println!(" Network requests tracked: {}", response_map.len()); + total_requests += response_map.len(); + } + + // Accumulate token usage + if let Some(usage) = spawn_result.usage() { + total_tokens += usage.total_tokens; + } + + // Check for recursive spawn_pages (pages that want to open more pages) + if let Some(more_pages) = spawn_result.spawn_pages() { + if !more_pages.is_empty() { + println!(" Additional pages to spawn: {:?}", more_pages); + } + } + + successes += 1; + } else { + println!( + "Page '{}' error: {}", + spawn_result.url, + spawn_result.error().unwrap_or("unknown") + ); + failures += 1; + } + println!(); + } + + println!("\n=== Summary ==="); + println!("Concurrent pages completed: {}", successes); + println!("Failed: {}", failures); + if total_tokens > 0 { + println!("Spawned pages total tokens: {}", total_tokens); + } + if total_bytes > 0.0 { + println!( + "Total bytes transferred: {:.0} ({:.2} KB)", + total_bytes, + total_bytes / 1024.0 + ); + println!("Total network requests: {}", total_requests); + } + } else { + println!("\n No URLs to spawn in new pages."); + println!(" (The model didn't use OpenPage action)"); + } + + // Print usage + println!("\n=== Token Usage ==="); + println!(" Prompt tokens: {}", result.usage.prompt_tokens); + println!(" Completion tokens: {}", result.usage.completion_tokens); + println!(" Total tokens: {}", result.usage.total_tokens); + + // Clean up + drop(browser); + handle.abort(); + + Ok(()) +} + +/// Get API configuration from environment variables. +fn get_api_config() -> Result<(String, String, String), Box> { + // Try OpenRouter first + if let Ok(key) = std::env::var("OPEN_ROUTER") { + return Ok(( + "https://openrouter.ai/api/v1/chat/completions".to_string(), + key, + std::env::var("MODEL_NAME") + .unwrap_or_else(|_| "anthropic/claude-3.5-sonnet".to_string()), + )); + } + + // Then try OpenAI + if let Ok(key) = std::env::var("OPENAI_API_KEY") { + return Ok(( + "https://api.openai.com/v1/chat/completions".to_string(), + key, + std::env::var("MODEL_NAME").unwrap_or_else(|_| "gpt-4o".to_string()), + )); + } + + // Then try local Ollama + if let Ok(url) = std::env::var("OLLAMA_URL") { + return Ok(( + url, + String::new(), + std::env::var("MODEL_NAME").unwrap_or_else(|_| "qwen2.5-vl".to_string()), + )); + } + + Err("Please set OPEN_ROUTER, OPENAI_API_KEY, or OLLAMA_URL environment variable".into()) +} diff --git a/spider_agent/examples/research.rs b/spider_agent/examples/research.rs new file mode 100644 index 000000000..668ec0e72 --- /dev/null +++ b/spider_agent/examples/research.rs @@ -0,0 +1,68 @@ +//! Research example using spider_agent. +//! +//! Run with: +//! ```sh +//! OPENAI_API_KEY=your-key SERPER_API_KEY=your-key cargo run --example research --features "openai search_serper" +//! ``` + +use spider_agent::{Agent, ResearchOptions, SearchOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let openai_key = + std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY environment variable required"); + let serper_key = + std::env::var("SERPER_API_KEY").expect("SERPER_API_KEY environment variable required"); + + let agent = Agent::builder() + .with_openai(openai_key, "gpt-4o-mini") + .with_search_serper(serper_key) + .build()?; + + println!("Researching: How do Tokio and async-std compare?\n"); + println!("This will search, fetch pages, extract data, and synthesize findings...\n"); + + let research = agent + .research( + "How do Tokio and async-std compare for Rust async programming?", + ResearchOptions::new() + .with_max_pages(3) + .with_search_options(SearchOptions::new().with_limit(5)) + .with_extraction_prompt( + "Extract key differences, pros, cons, and use cases for the async runtimes mentioned.", + ) + .with_synthesize(true), + ) + .await?; + + println!("=== Search Results ==="); + println!("Found {} results\n", research.search_results.len()); + + println!("=== Extracted Data ==="); + for (i, extraction) in research.extractions.iter().enumerate() { + println!("\n{}. {} ({})", i + 1, extraction.title, extraction.url); + println!( + " Extracted: {}", + serde_json::to_string(&extraction.extracted) + .unwrap_or_default() + .chars() + .take(200) + .collect::() + ); + } + + if let Some(summary) = research.summary { + println!("\n=== Summary ==="); + println!("{}", summary); + } + + println!("\n=== Token Usage ==="); + println!( + "Prompt: {}, Completion: {}, Total: {}", + research.usage.prompt_tokens, research.usage.completion_tokens, research.usage.total_tokens + ); + + Ok(()) +} diff --git a/spider_agent/examples/spider_cloud_ecommerce_competitor.rs b/spider_agent/examples/spider_cloud_ecommerce_competitor.rs new file mode 100644 index 000000000..63731ee23 --- /dev/null +++ b/spider_agent/examples/spider_cloud_ecommerce_competitor.rs @@ -0,0 +1,248 @@ +//! Real-world Spider Cloud example: e-commerce competitor intelligence. +//! +//! This flow turns a simple input into a multi-tool pipeline: +//! - `search` discover relevant product pages +//! - `links` expand page discovery from a seed URL +//! - `scrape` extract product-page content +//! - optional `transform` normalize output-ready content +//! - optional `ai_scrape` structured extraction (AI subscription required) +//! +//! Run: +//! ```bash +//! SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_ecommerce_competitor \ +//! -- "https://books.toscrape.com/" "travel books" +//! ``` +//! +//! Optional env vars: +//! - `SPIDER_CLOUD_API_URL` (default: `https://api.spider.cloud`) +//! - `SPIDER_CLOUD_TOOL_PREFIX` (default: `spider_cloud`) +//! - `SPIDER_CLOUD_ENABLE_AI_ROUTES=1` (required for `/ai/*` routes) +//! - `SPIDER_CLOUD_RETURN_FORMAT` (default: `markdown`, supports `raw|bytes|markdown|commonmark|text`) +//! - `SPIDER_CLOUD_INCLUDE_TRANSFORM=1` to explicitly run `/transform` + +use spider_agent::{Agent, SpiderCloudToolConfig}; + +#[derive(Debug, Clone)] +struct Step { + suffix: &'static str, + description: &'static str, + body: serde_json::Value, +} + +fn env_flag(name: &str) -> bool { + matches!( + std::env::var(name) + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn tool_name(prefix: &str, suffix: &str) -> String { + let p = prefix.trim().trim_end_matches('_'); + if p.is_empty() { + suffix.to_string() + } else { + format!("{}_{}", p, suffix) + } +} + +fn summarize_usage(value: &serde_json::Value) -> String { + let first = if let Some(arr) = value.as_array() { + arr.first() + } else { + Some(value) + }; + let Some(first) = first else { + return "empty-response".to_string(); + }; + + let status = first + .get("status") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let duration_ms = first + .get("duration_elapsed_ms") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + format!( + "status={}, duration_ms={}, total_cost={}", + status, duration_ms, total_cost + ) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = std::env::var("SPIDER_CLOUD_API_KEY") + .expect("SPIDER_CLOUD_API_KEY environment variable must be set"); + let api_url = + std::env::var("SPIDER_CLOUD_API_URL").unwrap_or_else(|_| "https://api.spider.cloud".into()); + let tool_prefix = + std::env::var("SPIDER_CLOUD_TOOL_PREFIX").unwrap_or_else(|_| "spider_cloud".into()); + let enable_ai_routes = env_flag("SPIDER_CLOUD_ENABLE_AI_ROUTES"); + let include_transform = env_flag("SPIDER_CLOUD_INCLUDE_TRANSFORM"); + let return_format = + std::env::var("SPIDER_CLOUD_RETURN_FORMAT").unwrap_or_else(|_| "markdown".into()); + + let args: Vec = std::env::args().skip(1).collect(); + let seed_url = args + .first() + .cloned() + .unwrap_or_else(|| "https://books.toscrape.com/".to_string()); + let market_query = args + .get(1) + .cloned() + .unwrap_or_else(|| "travel books".to_string()); + let detail_url = if seed_url.contains("books.toscrape.com") { + "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html".to_string() + } else { + seed_url.clone() + }; + + let cloud_cfg = SpiderCloudToolConfig::new(api_key) + .with_api_url(api_url.clone()) + .with_tool_name_prefix(tool_prefix.clone()) + .with_enable_ai_routes(enable_ai_routes); + let agent = Agent::builder() + .with_spider_cloud_config(cloud_cfg) + .build()?; + + println!("=== E-commerce Competitor Intelligence ==="); + println!("Seed URL: {}", seed_url); + println!("Market query: {}", market_query); + println!("API URL: {}", api_url); + println!("AI routes enabled: {}", enable_ai_routes); + println!("Return format: {}", return_format); + println!("Include transform: {}", include_transform); + println!(); + + let mut steps = vec![ + Step { + suffix: "search", + description: "discover category/product pages", + body: serde_json::json!({ + "search": format!("site:{} {}", seed_url.trim_start_matches("https://").trim_start_matches("http://").trim_end_matches('/'), market_query), + "num": 5, + "fetch_page_content": false + }), + }, + Step { + suffix: "links", + description: "extract internal links from seed", + body: serde_json::json!({ + "url": seed_url, + "limit": 2 + }), + }, + Step { + suffix: "scrape", + description: "extract content from a representative product URL", + body: serde_json::json!({ + "url": detail_url, + "return_format": return_format, + "metadata": true + }), + }, + ]; + + if include_transform { + steps.push(Step { + suffix: "transform", + description: "normalize content for downstream systems", + body: serde_json::json!({ + "url": detail_url, + "return_format": return_format, + "metadata": true + }), + }); + } + + if enable_ai_routes { + steps.push(Step { + suffix: "ai_scrape", + description: "AI structured extraction with schema", + body: serde_json::json!({ + "url": detail_url, + "prompt": "Extract title, price, availability, and product category.", + "cleaning_intent": "extraction", + "metadata": true, + "extraction_schema": { + "name": "product_intel", + "description": "Normalized product details", + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "price": { "type": "string" }, + "availability": { "type": "string" }, + "category": { "type": "string" } + }, + "required": ["title", "price", "availability"] + } + } + }), + }); + } + + for step in steps { + let name = tool_name(&tool_prefix, step.suffix); + let body = step.body.to_string(); + println!("Running {} ({})", name, step.description); + + match agent + .execute_custom_tool(&name, None, None, Some(&body)) + .await + { + Ok(result) => { + if !result.success { + println!( + " failed: HTTP {} body={}", + result.status, + result.body.chars().take(240).collect::() + ); + continue; + } + + let parsed = serde_json::from_str::(&result.body) + .unwrap_or(serde_json::Value::Null); + println!( + " ok: HTTP {} | {}", + result.status, + summarize_usage(&parsed) + ); + println!( + " preview: {}", + result.body.chars().take(180).collect::() + ); + } + Err(err) => println!(" error: {}", err), + } + } + + let usage = agent.usage(); + println!("\n=== Usage Snapshot ==="); + println!( + "Total custom tool calls: {}", + usage.total_custom_tool_calls() + ); + for (tool, count) in &usage.custom_tool_calls { + println!("- {}: {}", tool, count); + } + + Ok(()) +} diff --git a/spider_agent/examples/spider_cloud_end_to_end.rs b/spider_agent/examples/spider_cloud_end_to_end.rs new file mode 100644 index 000000000..58d292741 --- /dev/null +++ b/spider_agent/examples/spider_cloud_end_to_end.rs @@ -0,0 +1,576 @@ +//! End-to-end Spider Cloud pipeline from a single prompt. +//! +//! This example is designed for production-like workflows: +//! - takes one natural-language prompt +//! - plans a multi-step route sequence +//! - executes routes in order +//! - runs `unblocker` fallback when scrape fails +//! - optionally runs AI structured extraction when enabled +//! - prints a structured JSON execution report +//! +//! Run: +//! ```bash +//! SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_end_to_end \ +//! -- "Find top travel books on https://books.toscrape.com and return structured product fields" +//! ``` +//! +//! Optional env vars: +//! - `SPIDER_CLOUD_API_URL` (default: `https://api.spider.cloud`) +//! - `SPIDER_CLOUD_TOOL_PREFIX` (default: `spider_cloud`) +//! - `SPIDER_CLOUD_RETURN_FORMAT` (default: `markdown`, supports `raw|bytes|markdown|commonmark|text`) +//! - `SPIDER_CLOUD_ENABLE_AI_ROUTES=1` (required for `/ai/*` routes) +//! - `SPIDER_CLOUD_INCLUDE_TRANSFORM=1` (only if transform is explicitly needed) +//! - `SPIDER_CLOUD_FORCE_UNBLOCKER=1` (always include unblocker step) +//! - `SPIDER_FLOW_PROMPT` (fallback prompt if CLI args are not provided) + +use serde::Serialize; +use spider_agent::{Agent, SpiderCloudToolConfig}; + +#[derive(Debug, Clone)] +struct Step { + suffix: &'static str, + description: &'static str, + body: serde_json::Value, +} + +#[derive(Debug, Clone)] +struct PlanOptions { + return_format: String, + include_transform: bool, + force_unblocker: bool, + enable_ai_routes: bool, +} + +#[derive(Debug, Clone, Serialize)] +struct StepReport { + tool: String, + description: String, + success: bool, + http_status: Option, + status_field: Option, + duration_elapsed_ms: Option, + total_cost: Option, + preview: String, + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +struct RunReport { + prompt: String, + seed_url: String, + query: String, + return_format: String, + ai_routes_enabled: bool, + steps_planned: usize, + steps: Vec, + usage_total_custom_tool_calls: u64, + usage_by_tool: std::collections::BTreeMap, +} + +fn env_flag(name: &str) -> bool { + matches!( + std::env::var(name) + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn tool_name(prefix: &str, suffix: &str) -> String { + let p = prefix.trim().trim_end_matches('_'); + if p.is_empty() { + suffix.to_string() + } else { + format!("{}_{}", p, suffix) + } +} + +fn contains_any(haystack: &str, needles: &[&str]) -> bool { + needles.iter().any(|n| haystack.contains(n)) +} + +fn extract_first_url(prompt: &str) -> Option { + prompt + .split_whitespace() + .map(|t| t.trim_matches(|c: char| ",.;:!?()[]{}<>\"'".contains(c))) + .find(|t| t.starts_with("https://") || t.starts_with("http://")) + .map(|s| s.to_string()) +} + +fn query_from_prompt(prompt: &str, seed_url: &str) -> String { + if let Some(idx) = prompt.find(seed_url) { + let mut left = prompt[..idx].trim().to_string(); + for prep in [" on", " from", " at", " in", " for"] { + if left.to_ascii_lowercase().ends_with(prep) { + let new_len = left.len().saturating_sub(prep.len()); + left.truncate(new_len); + left = left.trim_end().to_string(); + break; + } + } + if !left.is_empty() { + return left; + } + } + + let tokens: Vec<&str> = prompt + .split_whitespace() + .filter(|t| !t.contains(seed_url)) + .collect(); + let q = tokens.join(" ").trim().to_string(); + if !q.is_empty() { + return q; + } + + format!( + "site:{} relevant pages", + seed_url + .trim_start_matches("https://") + .trim_start_matches("http://") + .trim_end_matches('/') + ) +} + +fn should_use_unblocker(prompt_lc: &str, force: bool) -> bool { + force + || contains_any( + prompt_lc, + &[ + "unblock", + "bypass", + "anti-bot", + "antibot", + "cloudflare", + "challenge", + "blocked", + "bot protection", + ], + ) +} + +fn should_use_ai_extract(prompt_lc: &str, ai_enabled: bool) -> bool { + ai_enabled + && contains_any( + prompt_lc, + &[ + "structured", + "schema", + "json output", + "typed output", + "extract fields", + "ai extract", + "ai scrape", + ], + ) +} + +fn build_steps(prompt: &str, seed_url: &str, query: &str, opts: &PlanOptions) -> Vec { + let prompt_lc = prompt.to_ascii_lowercase(); + let use_unblocker = should_use_unblocker(&prompt_lc, opts.force_unblocker); + let use_ai_extract = should_use_ai_extract(&prompt_lc, opts.enable_ai_routes); + + let mut steps = vec![ + Step { + suffix: "search", + description: "discover candidate URLs", + body: serde_json::json!({ + "search": query, + "num": 5, + "fetch_page_content": false + }), + }, + Step { + suffix: "links", + description: "extract first-layer internal links", + body: serde_json::json!({ + "url": seed_url, + "limit": 2 + }), + }, + Step { + suffix: "crawl", + description: "crawl nearby pages to widen coverage", + body: serde_json::json!({ + "url": seed_url, + "limit": 2, + "depth": 1, + "return_format": opts.return_format + }), + }, + Step { + suffix: "scrape", + description: "extract page content using selected return_format", + body: serde_json::json!({ + "url": seed_url, + "return_format": opts.return_format, + "metadata": true + }), + }, + ]; + + if use_unblocker { + steps.insert( + 3, + Step { + suffix: "unblocker", + description: "attempt anti-bot resistant retrieval", + body: serde_json::json!({ + "url": seed_url, + "return_format": opts.return_format, + "metadata": true + }), + }, + ); + } + + if opts.include_transform { + steps.push(Step { + suffix: "transform", + description: "optional post-processing transform", + body: serde_json::json!({ + "url": seed_url, + "return_format": opts.return_format, + "metadata": true + }), + }); + } + + if use_ai_extract { + steps.push(Step { + suffix: "ai_scrape", + description: "AI structured extraction", + body: serde_json::json!({ + "url": seed_url, + "prompt": "Extract a concise structured summary of the page with title, key_entities, and key_facts.", + "cleaning_intent": "extraction", + "metadata": true, + "extraction_schema": { + "name": "page_structured_summary", + "description": "Structured summary extracted from target page", + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "key_entities": { + "type": "array", + "items": { "type": "string" } + }, + "key_facts": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["title"] + } + } + }), + }); + } + + steps +} + +fn summarize_json_payload( + payload: &serde_json::Value, +) -> (Option, Option, Option) { + let first = if let Some(arr) = payload.as_array() { + arr.first().unwrap_or(payload) + } else { + payload + }; + + let status = first.get("status").and_then(|v| v.as_u64()); + let duration = first.get("duration_elapsed_ms").and_then(|v| v.as_u64()); + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()); + + (status, duration, total_cost) +} + +fn preview_from_body(body: &str, max: usize) -> String { + body.chars().take(max).collect::() +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = std::env::var("SPIDER_CLOUD_API_KEY") + .expect("SPIDER_CLOUD_API_KEY environment variable must be set"); + let api_url = + std::env::var("SPIDER_CLOUD_API_URL").unwrap_or_else(|_| "https://api.spider.cloud".into()); + let tool_prefix = + std::env::var("SPIDER_CLOUD_TOOL_PREFIX").unwrap_or_else(|_| "spider_cloud".into()); + let return_format = + std::env::var("SPIDER_CLOUD_RETURN_FORMAT").unwrap_or_else(|_| "markdown".into()); + let enable_ai_routes = env_flag("SPIDER_CLOUD_ENABLE_AI_ROUTES"); + let include_transform = env_flag("SPIDER_CLOUD_INCLUDE_TRANSFORM"); + let force_unblocker = env_flag("SPIDER_CLOUD_FORCE_UNBLOCKER"); + + let prompt = { + let args: Vec = std::env::args().skip(1).collect(); + if args.is_empty() { + std::env::var("SPIDER_FLOW_PROMPT").unwrap_or_else(|_| { + "Find top travel books on https://books.toscrape.com and return structured product fields" + .to_string() + }) + } else { + args.join(" ") + } + }; + + let seed_url = + extract_first_url(&prompt).unwrap_or_else(|| "https://books.toscrape.com/".into()); + let query = query_from_prompt(&prompt, &seed_url); + + let cloud_cfg = SpiderCloudToolConfig::new(api_key) + .with_api_url(api_url) + .with_tool_name_prefix(tool_prefix.clone()) + .with_enable_ai_routes(enable_ai_routes); + let agent = Agent::builder() + .with_spider_cloud_config(cloud_cfg) + .build()?; + + let plan_options = PlanOptions { + return_format: return_format.clone(), + include_transform, + force_unblocker, + enable_ai_routes, + }; + let steps = build_steps(&prompt, &seed_url, &query, &plan_options); + + println!("=== Spider Cloud End-to-End ==="); + println!("Prompt: {}", prompt); + println!("Seed URL: {}", seed_url); + println!("Query: {}", query); + println!("Return format: {}", return_format); + println!("AI routes enabled: {}", enable_ai_routes); + println!("Transform enabled: {}", include_transform); + println!("Force unblocker: {}", force_unblocker); + println!("Planned steps: {}", steps.len()); + + let mut reports = Vec::with_capacity(steps.len()); + + for step in steps { + let name = tool_name(&tool_prefix, step.suffix); + let body = step.body.to_string(); + println!("Running {} ({})", name, step.description); + + match agent + .execute_custom_tool(&name, None, None, Some(&body)) + .await + { + Ok(result) => { + let parsed = serde_json::from_str::(&result.body) + .unwrap_or(serde_json::Value::Null); + let (status_field, duration_elapsed_ms, total_cost) = + summarize_json_payload(&parsed); + + let preview = preview_from_body(&result.body, 240); + println!(" -> success={} http={}", result.success, result.status); + + reports.push(StepReport { + tool: name.clone(), + description: step.description.to_string(), + success: result.success, + http_status: Some(result.status), + status_field, + duration_elapsed_ms, + total_cost, + preview: preview.clone(), + error: if result.success { + None + } else { + Some(format!("HTTP {} failure", result.status)) + }, + }); + + if !result.success && step.suffix == "scrape" && !force_unblocker { + let fallback_name = tool_name(&tool_prefix, "unblocker"); + let fallback_body = serde_json::json!({ + "url": seed_url, + "return_format": return_format, + "metadata": true + }) + .to_string(); + + println!(" scrape failed, trying fallback {}", fallback_name); + match agent + .execute_custom_tool(&fallback_name, None, None, Some(&fallback_body)) + .await + { + Ok(fallback) => { + let parsed = serde_json::from_str::(&fallback.body) + .unwrap_or(serde_json::Value::Null); + let (status_field, duration_elapsed_ms, total_cost) = + summarize_json_payload(&parsed); + reports.push(StepReport { + tool: fallback_name.clone(), + description: "fallback unblocker after scrape failure".to_string(), + success: fallback.success, + http_status: Some(fallback.status), + status_field, + duration_elapsed_ms, + total_cost, + preview: preview_from_body(&fallback.body, 240), + error: if fallback.success { + None + } else { + Some(format!("HTTP {} fallback failure", fallback.status)) + }, + }); + } + Err(err) => { + reports.push(StepReport { + tool: fallback_name, + description: "fallback unblocker after scrape failure".to_string(), + success: false, + http_status: None, + status_field: None, + duration_elapsed_ms: None, + total_cost: None, + preview: String::new(), + error: Some(err.to_string()), + }); + } + } + } + } + Err(err) => { + reports.push(StepReport { + tool: name, + description: step.description.to_string(), + success: false, + http_status: None, + status_field: None, + duration_elapsed_ms: None, + total_cost: None, + preview: String::new(), + error: Some(err.to_string()), + }); + } + } + } + + let usage = agent.usage(); + let mut usage_by_tool = std::collections::BTreeMap::new(); + for (tool, count) in &usage.custom_tool_calls { + usage_by_tool.insert(tool.clone(), *count); + } + + let report = RunReport { + prompt, + seed_url, + query, + return_format, + ai_routes_enabled: enable_ai_routes, + steps_planned: reports.len(), + steps: reports, + usage_total_custom_tool_calls: usage.total_custom_tool_calls(), + usage_by_tool, + }; + + println!("\n=== End-to-End Report ==="); + println!("{}", serde_json::to_string_pretty(&report)?); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn names(steps: &[Step]) -> Vec<&'static str> { + steps.iter().map(|s| s.suffix).collect() + } + + #[test] + fn planner_includes_unblocker_on_blocked_intent() { + let opts = PlanOptions { + return_format: "markdown".to_string(), + include_transform: false, + force_unblocker: false, + enable_ai_routes: false, + }; + let steps = build_steps( + "Collect blocked pages from https://example.com behind anti-bot challenge", + "https://example.com", + "collect blocked pages", + &opts, + ); + assert!(names(&steps).contains(&"unblocker")); + } + + #[test] + fn planner_ai_scrape_only_when_ai_enabled_and_requested() { + let opts = PlanOptions { + return_format: "markdown".to_string(), + include_transform: false, + force_unblocker: false, + enable_ai_routes: true, + }; + let steps = build_steps( + "Use structured json output for https://example.com and extract fields", + "https://example.com", + "extract fields", + &opts, + ); + assert!(names(&steps).contains(&"ai_scrape")); + + let disabled_opts = PlanOptions { + enable_ai_routes: false, + ..opts + }; + let disabled_steps = build_steps( + "Use structured json output for https://example.com and extract fields", + "https://example.com", + "extract fields", + &disabled_opts, + ); + assert!(!names(&disabled_steps).contains(&"ai_scrape")); + } + + #[test] + fn planner_transform_is_opt_in() { + let base = PlanOptions { + return_format: "bytes".to_string(), + include_transform: false, + force_unblocker: false, + enable_ai_routes: false, + }; + let without = build_steps( + "Extract docs for https://example.com", + "https://example.com", + "extract docs", + &base, + ); + assert!(!names(&without).contains(&"transform")); + + let with = build_steps( + "Extract docs for https://example.com", + "https://example.com", + "extract docs", + &PlanOptions { + include_transform: true, + ..base + }, + ); + assert!(names(&with).contains(&"transform")); + } + + #[test] + fn query_builder_prefers_clean_left_side_of_url() { + let q = query_from_prompt( + "Find top travel books on https://books.toscrape.com and return structured fields", + "https://books.toscrape.com", + ); + assert_eq!(q, "Find top travel books"); + } +} diff --git a/spider_agent/examples/spider_cloud_jobs_pipeline.rs b/spider_agent/examples/spider_cloud_jobs_pipeline.rs new file mode 100644 index 000000000..f1b071979 --- /dev/null +++ b/spider_agent/examples/spider_cloud_jobs_pipeline.rs @@ -0,0 +1,264 @@ +//! Real-world Spider Cloud example: job market intelligence pipeline. +//! +//! This flow demonstrates automated hiring-intel collection: +//! - `search` discover current postings/pages +//! - `crawl` gather nearby pages for context +//! - `unblocker` handle harder anti-bot pages +//! - `scrape` extract listing text/content +//! - optional `transform` normalize output for alerting/storage +//! - optional `ai_scrape` for typed extraction +//! +//! Run: +//! ```bash +//! SPIDER_CLOUD_API_KEY=your-key cargo run -p spider_agent --example spider_cloud_jobs_pipeline \ +//! -- "rust engineer remote" "https://remoteok.com/remote-rust-jobs" +//! ``` +//! +//! Optional env vars: +//! - `SPIDER_CLOUD_API_URL` (default: `https://api.spider.cloud`) +//! - `SPIDER_CLOUD_TOOL_PREFIX` (default: `spider_cloud`) +//! - `SPIDER_CLOUD_ENABLE_AI_ROUTES=1` (required for `/ai/*` routes) +//! - `SPIDER_CLOUD_RETURN_FORMAT` (default: `markdown`, supports `raw|bytes|markdown|commonmark|text`) +//! - `SPIDER_CLOUD_INCLUDE_TRANSFORM=1` to explicitly run `/transform` + +use spider_agent::{Agent, SpiderCloudToolConfig}; + +#[derive(Debug, Clone)] +struct Step { + suffix: &'static str, + description: &'static str, + body: serde_json::Value, +} + +fn env_flag(name: &str) -> bool { + matches!( + std::env::var(name) + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn tool_name(prefix: &str, suffix: &str) -> String { + let p = prefix.trim().trim_end_matches('_'); + if p.is_empty() { + suffix.to_string() + } else { + format!("{}_{}", p, suffix) + } +} + +fn summarize_usage(value: &serde_json::Value) -> String { + let first = if let Some(arr) = value.as_array() { + arr.first() + } else { + Some(value) + }; + let Some(first) = first else { + return "empty-response".to_string(); + }; + + let status = first + .get("status") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let duration_ms = first + .get("duration_elapsed_ms") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + format!( + "status={}, duration_ms={}, total_cost={}", + status, duration_ms, total_cost + ) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = std::env::var("SPIDER_CLOUD_API_KEY") + .expect("SPIDER_CLOUD_API_KEY environment variable must be set"); + let api_url = + std::env::var("SPIDER_CLOUD_API_URL").unwrap_or_else(|_| "https://api.spider.cloud".into()); + let tool_prefix = + std::env::var("SPIDER_CLOUD_TOOL_PREFIX").unwrap_or_else(|_| "spider_cloud".into()); + let enable_ai_routes = env_flag("SPIDER_CLOUD_ENABLE_AI_ROUTES"); + let include_transform = env_flag("SPIDER_CLOUD_INCLUDE_TRANSFORM"); + let return_format = + std::env::var("SPIDER_CLOUD_RETURN_FORMAT").unwrap_or_else(|_| "markdown".into()); + + let args: Vec = std::env::args().skip(1).collect(); + let search_query = args + .first() + .cloned() + .unwrap_or_else(|| "rust engineer remote".to_string()); + let seed_url = args + .get(1) + .cloned() + .unwrap_or_else(|| "https://remoteok.com/remote-rust-jobs".to_string()); + + let cloud_cfg = SpiderCloudToolConfig::new(api_key) + .with_api_url(api_url.clone()) + .with_tool_name_prefix(tool_prefix.clone()) + .with_enable_ai_routes(enable_ai_routes); + let agent = Agent::builder() + .with_spider_cloud_config(cloud_cfg) + .build()?; + + println!("=== Job Market Intelligence Pipeline ==="); + println!("Search query: {}", search_query); + println!("Seed URL: {}", seed_url); + println!("API URL: {}", api_url); + println!("AI routes enabled: {}", enable_ai_routes); + println!("Return format: {}", return_format); + println!("Include transform: {}", include_transform); + println!(); + + let mut steps = vec![ + Step { + suffix: "search", + description: "discover relevant job pages", + body: serde_json::json!({ + "search": search_query, + "num": 5, + "fetch_page_content": false + }), + }, + Step { + suffix: "crawl", + description: "crawl adjacent pages for related openings", + body: serde_json::json!({ + "url": seed_url, + "limit": 2, + "depth": 1, + "return_format": return_format + }), + }, + Step { + suffix: "unblocker", + description: "attempt anti-bot resistant retrieval", + body: serde_json::json!({ + "url": seed_url, + "return_format": return_format, + "metadata": true + }), + }, + Step { + suffix: "scrape", + description: "extract normalized listing text", + body: serde_json::json!({ + "url": seed_url, + "return_format": return_format, + "metadata": true + }), + }, + ]; + + if include_transform { + steps.push(Step { + suffix: "transform", + description: "produce transform-ready output", + body: serde_json::json!({ + "url": seed_url, + "return_format": return_format, + "metadata": true + }), + }); + } + + if enable_ai_routes { + steps.push(Step { + suffix: "ai_scrape", + description: "AI extraction for hiring signal fields", + body: serde_json::json!({ + "url": seed_url, + "prompt": "Extract up to 5 job postings with title, company, location, and url.", + "cleaning_intent": "extraction", + "metadata": true, + "extraction_schema": { + "name": "job_postings", + "description": "Structured job listing extraction", + "schema": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "company": { "type": "string" }, + "location": { "type": "string" }, + "url": { "type": "string" } + }, + "required": ["title", "company"] + } + } + }, + "required": ["jobs"] + } + } + }), + }); + } + + for step in steps { + let name = tool_name(&tool_prefix, step.suffix); + let body = step.body.to_string(); + println!("Running {} ({})", name, step.description); + + match agent + .execute_custom_tool(&name, None, None, Some(&body)) + .await + { + Ok(result) => { + if !result.success { + println!( + " failed: HTTP {} body={}", + result.status, + result.body.chars().take(240).collect::() + ); + continue; + } + + let parsed = serde_json::from_str::(&result.body) + .unwrap_or(serde_json::Value::Null); + println!( + " ok: HTTP {} | {}", + result.status, + summarize_usage(&parsed) + ); + println!( + " preview: {}", + result.body.chars().take(180).collect::() + ); + } + Err(err) => println!(" error: {}", err), + } + } + + let usage = agent.usage(); + println!("\n=== Usage Snapshot ==="); + println!( + "Total custom tool calls: {}", + usage.total_custom_tool_calls() + ); + for (tool, count) in &usage.custom_tool_calls { + println!("- {}: {}", tool, count); + } + + Ok(()) +} diff --git a/spider_agent/examples/spider_cloud_prompt_flows.rs b/spider_agent/examples/spider_cloud_prompt_flows.rs new file mode 100644 index 000000000..386078d05 --- /dev/null +++ b/spider_agent/examples/spider_cloud_prompt_flows.rs @@ -0,0 +1,411 @@ +//! Spider Cloud prompt-driven tool flow example. +//! +//! A single natural-language prompt triggers one or more Spider Cloud tool +//! calls: search, scrape, crawl, links, transform, unblocker, and optional +//! AI routes. +//! +//! Run with: +//! ```bash +//! SPIDER_CLOUD_API_KEY=your-key cargo run --example spider_cloud_prompt_flows -p spider_agent +//! ``` +//! +//! Example prompt: +//! ```text +//! run all flows for https://books.toscrape.com/ including search scrape crawl links transform unblocker +//! ``` +//! +//! Optional env vars: +//! - `SPIDER_CLOUD_API_URL` (default: `https://api.spider.cloud`) +//! - `SPIDER_CLOUD_TOOL_PREFIX` (default: `spider_cloud`) +//! - `SPIDER_CLOUD_ENABLE_AI_ROUTES=1` (AI plan required) +//! - `SPIDER_FLOW_PROMPT` (fallback prompt if no CLI args passed) +//! +//! AI prompt example: +//! ```text +//! run all flows for https://books.toscrape.com/ and include ai routes +//! ``` + +use spider_agent::{Agent, SpiderCloudToolConfig}; + +#[derive(Debug, Clone)] +struct ToolFlow { + suffix: &'static str, + body: serde_json::Value, + description: &'static str, +} + +fn env_flag(name: &str) -> bool { + matches!( + std::env::var(name) + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn extract_first_url(prompt: &str) -> Option { + prompt + .split_whitespace() + .map(|t| t.trim_matches(|c: char| ",.;:!?()[]{}<>\"'".contains(c))) + .find(|t| t.starts_with("https://") || t.starts_with("http://")) + .map(|s| s.to_string()) +} + +fn contains_any(haystack: &str, needles: &[&str]) -> bool { + needles.iter().any(|n| haystack.contains(n)) +} + +fn wants_route(prompt: &str, run_all: bool, keywords: &[&str]) -> bool { + run_all || contains_any(prompt, keywords) +} + +fn tool_name(prefix: &str, suffix: &str) -> String { + let p = prefix.trim().trim_end_matches('_'); + if p.is_empty() { + suffix.to_string() + } else { + format!("{}_{}", p, suffix) + } +} + +fn summarize_usage(value: &serde_json::Value) -> String { + let first = if let Some(arr) = value.as_array() { + arr.first() + } else { + Some(value) + }; + + let Some(first) = first else { + return "empty-response".to_string(); + }; + + let status = first + .get("status") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let duration_ms = first + .get("duration_elapsed_ms") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + format!( + "status={}, duration_ms={}, total_cost={}", + status, duration_ms, total_cost + ) +} + +fn build_flows(prompt: &str, enable_ai_routes: bool) -> Vec { + let p = prompt.to_ascii_lowercase(); + let run_all = contains_any(&p, &["all flows", "all tools", "everything"]); + + let seed_url = + extract_first_url(prompt).unwrap_or_else(|| "https://books.toscrape.com/".into()); + let detail_url = if seed_url.contains("books.toscrape.com") { + "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html".to_string() + } else { + seed_url.clone() + }; + + let mut flows = Vec::new(); + + if wants_route(&p, run_all, &["search"]) { + flows.push(ToolFlow { + suffix: "search", + description: "Spider Cloud /search", + body: serde_json::json!({ + "search": "books to scrape", + "num": 3, + "fetch_page_content": false + }), + }); + } + if wants_route(&p, run_all, &["scrape"]) { + flows.push(ToolFlow { + suffix: "scrape", + description: "Spider Cloud /scrape", + body: serde_json::json!({ + "url": detail_url, + "return_format": "markdown", + "limit": 1 + }), + }); + } + if wants_route(&p, run_all, &["crawl"]) { + flows.push(ToolFlow { + suffix: "crawl", + description: "Spider Cloud /crawl", + body: serde_json::json!({ + "url": seed_url, + "limit": 1, + "return_format": "raw" + }), + }); + } + if wants_route(&p, run_all, &["links"]) { + flows.push(ToolFlow { + suffix: "links", + description: "Spider Cloud /links", + body: serde_json::json!({ + "url": seed_url, + "limit": 1 + }), + }); + } + if wants_route(&p, run_all, &["transform"]) { + flows.push(ToolFlow { + suffix: "transform", + description: "Spider Cloud /transform", + body: serde_json::json!({ + "url": detail_url, + "return_format": "markdown", + "limit": 1 + }), + }); + } + if wants_route( + &p, + run_all, + &["unblocker", "unblock", "bypass", "anti-bot", "antibot"], + ) { + flows.push(ToolFlow { + suffix: "unblocker", + description: "Spider Cloud /unblocker", + body: serde_json::json!({ + "url": detail_url, + "return_format": "raw" + }), + }); + } + + let wants_ai_routes = enable_ai_routes + && wants_route( + &p, + run_all, + &[ + " ai", + "ai ", + "/ai/", + "/ai", + "ai routes", + "ai route", + "ai tools", + ], + ); + + if wants_ai_routes { + flows.push(ToolFlow { + suffix: "ai_search", + description: "Spider Cloud /ai/search (AI subscription)", + body: serde_json::json!({ + "url": seed_url, + "prompt": "find key pages about books", + "limit": 2 + }), + }); + flows.push(ToolFlow { + suffix: "ai_scrape", + description: "Spider Cloud /ai/scrape (AI subscription)", + body: serde_json::json!({ + "url": detail_url, + "prompt": "extract title, price, availability", + "cleaning_intent": "extraction" + }), + }); + flows.push(ToolFlow { + suffix: "ai_crawl", + description: "Spider Cloud /ai/crawl (AI subscription)", + body: serde_json::json!({ + "url": seed_url, + "prompt": "crawl product pages and extract titles", + "limit": 2 + }), + }); + flows.push(ToolFlow { + suffix: "ai_links", + description: "Spider Cloud /ai/links (AI subscription)", + body: serde_json::json!({ + "url": seed_url, + "prompt": "find product detail links", + "limit": 3 + }), + }); + flows.push(ToolFlow { + suffix: "ai_browser", + description: "Spider Cloud /ai/browser (AI subscription)", + body: serde_json::json!({ + "url": detail_url, + "prompt": "open the page and extract title, price, and availability", + "cleaning_intent": "general" + }), + }); + } + + if flows.is_empty() { + flows.push(ToolFlow { + suffix: "search", + description: "Spider Cloud /search (default)", + body: serde_json::json!({ + "search": "books to scrape", + "num": 3, + "fetch_page_content": false + }), + }); + flows.push(ToolFlow { + suffix: "scrape", + description: "Spider Cloud /scrape (default)", + body: serde_json::json!({ + "url": detail_url, + "return_format": "markdown", + "limit": 1 + }), + }); + } + + flows +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let api_key = std::env::var("SPIDER_CLOUD_API_KEY") + .expect("SPIDER_CLOUD_API_KEY environment variable must be set"); + let api_url = + std::env::var("SPIDER_CLOUD_API_URL").unwrap_or_else(|_| "https://api.spider.cloud".into()); + let tool_prefix = + std::env::var("SPIDER_CLOUD_TOOL_PREFIX").unwrap_or_else(|_| "spider_cloud".into()); + let enable_ai_routes = env_flag("SPIDER_CLOUD_ENABLE_AI_ROUTES"); + + let prompt = { + let args: Vec = std::env::args().skip(1).collect(); + if args.is_empty() { + std::env::var("SPIDER_FLOW_PROMPT").unwrap_or_else(|_| { + "run all flows for https://books.toscrape.com/ including search scrape crawl links transform unblocker" + .to_string() + }) + } else { + args.join(" ") + } + }; + + let cloud_cfg = SpiderCloudToolConfig::new(api_key) + .with_api_url(api_url.clone()) + .with_tool_name_prefix(tool_prefix.clone()) + .with_enable_ai_routes(enable_ai_routes); + let agent = Agent::builder() + .with_spider_cloud_config(cloud_cfg) + .build()?; + + println!("=== Spider Cloud Prompt Flows ==="); + println!("Prompt: {}", prompt); + println!("API URL: {}", api_url); + println!("Tool prefix: {}", tool_prefix); + println!("AI routes enabled: {}", enable_ai_routes); + println!(); + + let flows = build_flows(&prompt, enable_ai_routes); + println!("Planned {} tool call(s):", flows.len()); + for flow in &flows { + println!( + "- {} ({})", + tool_name(&tool_prefix, flow.suffix), + flow.description + ); + } + println!(); + + for flow in flows { + let name = tool_name(&tool_prefix, flow.suffix); + let body = flow.body.to_string(); + println!("Running {} ...", name); + + match agent + .execute_custom_tool(&name, None, None, Some(&body)) + .await + { + Ok(result) => { + if result.success { + let json_value = serde_json::from_str::(&result.body) + .unwrap_or(serde_json::Value::Null); + println!( + " ok: HTTP {} | {}", + result.status, + summarize_usage(&json_value) + ); + } else { + println!( + " failed: HTTP {} body={}", + result.status, + result.body.chars().take(200).collect::() + ); + } + } + Err(err) => { + println!(" error: {}", err); + } + } + } + + let usage = agent.usage(); + println!("\n=== Agent Usage Snapshot ==="); + println!( + "Total custom tool calls: {}", + usage.total_custom_tool_calls() + ); + for (tool, count) in &usage.custom_tool_calls { + println!("- {}: {}", tool, count); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn has_suffix(flows: &[ToolFlow], suffix: &str) -> bool { + flows.iter().any(|f| f.suffix == suffix) + } + + #[test] + fn base_routes_selected_for_all_flows_prompt() { + let flows = build_flows("run all flows for https://example.com", false); + assert!(has_suffix(&flows, "search")); + assert!(has_suffix(&flows, "scrape")); + assert!(has_suffix(&flows, "crawl")); + assert!(has_suffix(&flows, "links")); + assert!(has_suffix(&flows, "transform")); + assert!(has_suffix(&flows, "unblocker")); + assert!(!has_suffix(&flows, "ai_search")); + } + + #[test] + fn ai_routes_selected_when_enabled_and_requested() { + let flows = build_flows("include ai routes for https://example.com", true); + assert!(has_suffix(&flows, "ai_search")); + assert!(has_suffix(&flows, "ai_scrape")); + assert!(has_suffix(&flows, "ai_crawl")); + assert!(has_suffix(&flows, "ai_links")); + assert!(has_suffix(&flows, "ai_browser")); + } + + #[test] + fn defaults_to_search_and_scrape_when_prompt_has_no_route_hints() { + let flows = build_flows("do something useful", false); + assert_eq!(flows.len(), 2); + assert!(has_suffix(&flows, "search")); + assert!(has_suffix(&flows, "scrape")); + } +} diff --git a/spider_agent/src/agent.rs b/spider_agent/src/agent.rs new file mode 100644 index 000000000..b02ca520d --- /dev/null +++ b/spider_agent/src/agent.rs @@ -0,0 +1,1501 @@ +//! Core Agent struct and builder for spider_agent. + +use crate::config::{AgentConfig, UsageSnapshot, UsageStats}; +#[cfg(feature = "search")] +use crate::config::{ResearchOptions, SearchOptions}; +use crate::error::{AgentError, AgentResult}; +#[cfg(feature = "search")] +use crate::llm::TokenUsage; +use crate::llm::{CompletionOptions, CompletionResponse, LLMProvider, Message}; +use crate::memory::AgentMemory; +use crate::tools::{CustomTool, CustomToolRegistry, CustomToolResult, SpiderCloudToolConfig}; +use std::sync::Arc; +use tokio::sync::Semaphore; + +#[cfg(feature = "search")] +use crate::search::{SearchProvider, SearchResults}; + +#[cfg(feature = "chrome")] +use crate::browser::BrowserContext; + +#[cfg(feature = "webdriver")] +use crate::webdriver::WebDriverContext; + +#[cfg(feature = "fs")] +use crate::temp::TempStorage; + +/// Multimodal agent for web automation and research. +/// +/// Designed to be wrapped in `Arc` for concurrent access. +/// +/// # Example +/// ```ignore +/// use spider_agent::{Agent, AgentConfig}; +/// use std::sync::Arc; +/// +/// let agent = Arc::new(Agent::builder() +/// .with_openai("sk-...", "gpt-4o") +/// .with_search_serper("serper-key") +/// .build()?); +/// +/// // Spawn concurrent tasks +/// let agent_clone = agent.clone(); +/// tokio::spawn(async move { +/// agent_clone.search("rust web frameworks").await +/// }); +/// ``` +pub struct Agent { + /// LLM provider for inference. + llm: Option>, + + /// HTTP client for requests. + client: reqwest::Client, + + /// Search provider (if configured). + #[cfg(feature = "search")] + search_provider: Option>, + + /// Browser context for Chrome automation. + #[cfg(feature = "chrome")] + browser: Option, + + /// WebDriver context for browser automation. + #[cfg(feature = "webdriver")] + webdriver: Option, + + /// Temporary storage for large operations. + #[cfg(feature = "fs")] + temp_storage: Option, + + /// Session memory (lock-free via DashMap). + memory: AgentMemory, + + /// Concurrency semaphore for LLM calls. + llm_semaphore: Arc, + + /// Configuration. + config: AgentConfig, + + /// Usage statistics (atomic counters for lock-free updates). + usage: Arc, + + /// Custom tool registry. + custom_tools: CustomToolRegistry, +} + +impl Agent { + /// Create a new agent builder. + pub fn builder() -> AgentBuilder { + AgentBuilder::new() + } + + /// Returns a reference to the underlying HTTP client. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + + // ==================== Search Methods ==================== + + /// Search the web and return results. + #[cfg(feature = "search")] + pub async fn search(&self, query: &str) -> AgentResult { + self.search_with_options(query, SearchOptions::default()) + .await + } + + /// Search with custom options. + #[cfg(feature = "search")] + pub async fn search_with_options( + &self, + query: &str, + options: SearchOptions, + ) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_search_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let provider = self + .search_provider + .as_ref() + .ok_or(AgentError::NotConfigured("search provider"))?; + + self.usage.increment_search_calls(); + + provider + .search(query, &options, &self.client) + .await + .map_err(AgentError::Search) + } + + // ==================== LLM Methods ==================== + + /// Send a prompt to the LLM and get a response. + pub async fn prompt(&self, messages: Vec) -> AgentResult { + let response = self.complete(messages).await?; + Ok(response.content) + } + + /// Send a completion request with full options. + pub async fn complete(&self, messages: Vec) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_llm_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + if let Some(limit) = self.usage.check_token_limits(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let llm = self + .llm + .as_ref() + .ok_or(AgentError::NotConfigured("LLM provider"))?; + + let _permit = self + .llm_semaphore + .acquire() + .await + .map_err(|_| AgentError::Llm("Failed to acquire semaphore".to_string()))?; + + let options = CompletionOptions { + temperature: self.config.temperature, + max_tokens: self.config.max_tokens, + json_mode: self.config.json_mode, + }; + + self.usage.increment_llm_calls(); + + let response = llm.complete(messages, &options, &self.client).await?; + + // Track token usage + self.usage.add_tokens( + response.usage.prompt_tokens as u64, + response.usage.completion_tokens as u64, + ); + + Ok(response) + } + + // ==================== Extraction Methods ==================== + + /// Extract structured data from HTML using the LLM. + pub async fn extract(&self, html: &str, prompt: &str) -> AgentResult { + let cleaned_html = self.clean_html(html); + let truncated = self.truncate_html(&cleaned_html); + + let messages = vec![ + Message::system( + "You are a data extraction assistant. Extract the requested information from the HTML and return it as JSON.", + ), + Message::user(format!( + "Extract the following from this HTML:\n\n{}\n\nHTML:\n{}", + prompt, truncated + )), + ]; + + let response = self.complete(messages).await?; + self.parse_json(&response.content) + } + + /// Extract data with a JSON schema for structured output. + pub async fn extract_structured( + &self, + html: &str, + schema: &serde_json::Value, + ) -> AgentResult { + let cleaned_html = self.clean_html(html); + let truncated = self.truncate_html(&cleaned_html); + + let messages = vec![ + Message::system( + "You are a data extraction assistant. Extract data matching the provided JSON schema.", + ), + Message::user(format!( + "Extract data matching this schema:\n{}\n\nFrom this HTML:\n{}", + serde_json::to_string_pretty(schema).unwrap_or_default(), + truncated + )), + ]; + + let response = self.complete(messages).await?; + self.parse_json(&response.content) + } + + // ==================== HTTP Methods ==================== + + /// Fetch a URL and return the HTML content. + pub async fn fetch(&self, url: &str) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_fetch_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + self.usage.increment_fetch_calls(); + + let response = self.client.get(url).send().await?; + + let status = response.status(); + let headers = response.headers().clone(); + + let content_type = headers + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("") + .to_string(); + + let html = response.text().await?; + + Ok(FetchResult { + url: url.to_string(), + status: status.as_u16(), + content_type, + html, + }) + } + + // ==================== Research Methods ==================== + + /// Research a topic using search and extraction. + #[cfg(feature = "search")] + pub async fn research( + &self, + topic: &str, + options: ResearchOptions, + ) -> AgentResult { + // Search for the topic + let search_opts = options + .search_options + .clone() + .unwrap_or_else(|| SearchOptions::new().with_limit(options.max_pages.max(5))); + + let search_results = self.search_with_options(topic, search_opts).await?; + + if search_results.is_empty() { + return Ok(ResearchResult { + topic: topic.to_string(), + search_results, + extractions: Vec::new(), + summary: None, + usage: TokenUsage::default(), + }); + } + + // Extract from each result + let extraction_prompt = options.extraction_prompt.clone().unwrap_or_else(|| { + format!( + "Extract key information relevant to: {}. Include facts, data points, and insights.", + topic + ) + }); + + let mut extractions = Vec::new(); + let mut total_usage = TokenUsage::default(); + + let max_pages = options.max_pages.min(search_results.results.len()); + + for result in search_results.results.iter().take(max_pages) { + // Fetch page + match self.fetch(&result.url).await { + Ok(fetch_result) => { + // Extract + match self.extract(&fetch_result.html, &extraction_prompt).await { + Ok(extracted) => { + extractions.push(PageExtraction { + url: result.url.clone(), + title: result.title.clone(), + extracted, + }); + } + Err(e) => { + log::warn!("Extraction failed for {}: {}", result.url, e); + } + } + } + Err(e) => { + log::warn!("Fetch failed for {}: {}", result.url, e); + } + } + } + + // Synthesize if requested + let summary = if options.synthesize && !extractions.is_empty() { + match self.synthesize_research(topic, &extractions).await { + Ok((summary, usage)) => { + total_usage.accumulate(&usage); + Some(summary) + } + Err(e) => { + log::warn!("Synthesis failed: {}", e); + None + } + } + } else { + None + }; + + Ok(ResearchResult { + topic: topic.to_string(), + search_results, + extractions, + summary, + usage: total_usage, + }) + } + + /// Synthesize research findings into a summary. + #[cfg(feature = "search")] + async fn synthesize_research( + &self, + topic: &str, + extractions: &[PageExtraction], + ) -> AgentResult<(String, TokenUsage)> { + let mut context = String::new(); + for (i, extraction) in extractions.iter().enumerate() { + context.push_str(&format!( + "\n\nSource {} ({}): {}\n{}", + i + 1, + extraction.url, + extraction.title, + serde_json::to_string_pretty(&extraction.extracted).unwrap_or_default() + )); + } + + let messages = vec![ + Message::system( + "You are a research synthesis assistant. Summarize the findings from multiple sources into a coherent response.", + ), + Message::user(format!( + "Topic: {}\n\nSources:{}\n\nProvide a comprehensive summary of the findings, citing sources where appropriate. Return as JSON with a 'summary' field.", + topic, context + )), + ]; + + let response = self.complete(messages).await?; + let json = self.parse_json(&response.content)?; + let summary = json + .get("summary") + .and_then(|v| v.as_str()) + .unwrap_or(&response.content) + .to_string(); + + Ok((summary, response.usage)) + } + + // ==================== Memory Methods ==================== + + /// Get a value from memory (lock-free). + pub fn memory_get(&self, key: &str) -> Option { + self.memory.get(key) + } + + /// Set a value in memory (lock-free). + pub fn memory_set(&self, key: &str, value: serde_json::Value) { + self.memory.set(key, value); + } + + /// Clear all memory (lock-free). + pub fn memory_clear(&self) { + self.memory.clear(); + } + + /// Get the memory instance for direct access. + pub fn memory(&self) -> &AgentMemory { + &self.memory + } + + // ==================== Usage Methods ==================== + + /// Get a snapshot of usage statistics. + pub fn usage(&self) -> UsageSnapshot { + self.usage.snapshot() + } + + /// Get the raw usage stats for direct access. + pub fn usage_stats(&self) -> &Arc { + &self.usage + } + + /// Reset usage statistics. + pub fn reset_usage(&self) { + self.usage.reset(); + } + + // ==================== Custom Tool Methods ==================== + + /// Register a custom tool. + pub fn register_custom_tool(&self, tool: CustomTool) { + self.custom_tools.register(tool); + } + + /// Remove a custom tool. + pub fn remove_custom_tool(&self, name: &str) -> bool { + self.custom_tools.remove(name).is_some() + } + + /// List all registered custom tools. + pub fn list_custom_tools(&self) -> Vec { + self.custom_tools.list() + } + + /// Check if a custom tool is registered. + pub fn has_custom_tool(&self, name: &str) -> bool { + self.custom_tools.contains(name) + } + + /// Execute a custom tool by name. + /// + /// # Arguments + /// * `name` - The registered tool name + /// * `path` - Optional path to append to the base URL + /// * `query` - Optional query parameters + /// * `body` - Optional request body + pub async fn execute_custom_tool( + &self, + name: &str, + path: Option<&str>, + query: Option<&[(&str, &str)]>, + body: Option<&str>, + ) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_custom_tool_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + // Track the call + self.usage.increment_custom_tool_calls(name); + + // Execute the tool + self.custom_tools + .execute(name, &self.client, path, query, body) + .await + } + + /// Execute a custom tool and parse the JSON response. + pub async fn execute_custom_tool_json( + &self, + name: &str, + path: Option<&str>, + query: Option<&[(&str, &str)]>, + body: Option<&str>, + ) -> AgentResult { + let result = self.execute_custom_tool(name, path, query, body).await?; + serde_json::from_str(&result.body).map_err(AgentError::Json) + } + + /// Get the custom tool registry for direct access. + pub fn custom_tool_registry(&self) -> &CustomToolRegistry { + &self.custom_tools + } + + /// Register Spider Cloud routes as custom tools. + /// + /// Core routes (`/crawl`, `/scrape`, `/search`, `/links`, `/transform`, + /// `/unblocker`) are enabled by default. AI routes are gated and disabled + /// by default. + /// + /// Returns the number of tools registered. + pub fn register_spider_cloud(&self, config: SpiderCloudToolConfig) -> usize { + self.custom_tools.register_spider_cloud(&config) + } + + // ==================== Browser Methods ==================== + + /// Get the browser context if configured. + #[cfg(feature = "chrome")] + pub fn browser(&self) -> Option<&BrowserContext> { + self.browser.as_ref() + } + + /// Navigate to a URL using the browser. + #[cfg(feature = "chrome")] + pub async fn navigate(&self, url: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .navigate(url) + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Get HTML from the current browser page. + #[cfg(feature = "chrome")] + pub async fn browser_html(&self) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .html() + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Take a screenshot of the current browser page. + #[cfg(feature = "chrome")] + pub async fn screenshot(&self) -> AgentResult> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .screenshot() + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Open a new page/tab in the browser. + #[cfg(feature = "chrome")] + pub async fn new_page(&self) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .clone_page() + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Open a new page and navigate to URL. + #[cfg(feature = "chrome")] + pub async fn new_page_with_url( + &self, + url: &str, + ) -> AgentResult> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .new_page_with_url(url) + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Click an element in the browser. + #[cfg(feature = "chrome")] + pub async fn click(&self, selector: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .click(selector) + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Type text into an element in the browser. + #[cfg(feature = "chrome")] + pub async fn type_text(&self, selector: &str, text: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let browser = self + .browser + .as_ref() + .ok_or(AgentError::NotConfigured("browser"))?; + + self.usage.increment_webbrowser_calls(); + + browser + .type_text(selector, text) + .await + .map_err(|e| AgentError::Browser(e.to_string())) + } + + /// Extract from the current browser page using the LLM. + #[cfg(feature = "chrome")] + pub async fn extract_page(&self, prompt: &str) -> AgentResult { + let html = self.browser_html().await?; + self.extract(&html, prompt).await + } + + // ==================== WebDriver Methods ==================== + + /// Get the WebDriver context if configured. + #[cfg(feature = "webdriver")] + pub fn webdriver(&self) -> Option<&WebDriverContext> { + self.webdriver.as_ref() + } + + /// Navigate using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_navigate(&self, url: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .navigate(url) + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + /// Get HTML from WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_html(&self) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .html() + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + /// Take a screenshot using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_screenshot(&self) -> AgentResult> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .screenshot() + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + /// Click an element using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_click(&self, selector: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .click(selector) + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + /// Type text into an element using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_type_text(&self, selector: &str, text: &str) -> AgentResult<()> { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .type_text(selector, text) + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + /// Extract from the current WebDriver page using the LLM. + #[cfg(feature = "webdriver")] + pub async fn webdriver_extract_page(&self, prompt: &str) -> AgentResult { + // Note: webdriver_html already tracks the webbrowser call + let html = self.webdriver_html().await?; + self.extract(&html, prompt).await + } + + /// Open a new tab using WebDriver. + #[cfg(feature = "webdriver")] + pub async fn webdriver_new_tab(&self) -> AgentResult { + // Check limits before proceeding + if let Some(limit) = self.usage.check_webbrowser_limit(&self.config.limits) { + return Err(AgentError::LimitExceeded(limit)); + } + + let driver = self + .webdriver + .as_ref() + .ok_or(AgentError::NotConfigured("webdriver"))?; + + self.usage.increment_webbrowser_calls(); + + driver + .new_tab() + .await + .map_err(|e| AgentError::WebDriver(e.to_string())) + } + + // ==================== Temp Storage Methods ==================== + + /// Get the temp storage if configured. + #[cfg(feature = "fs")] + pub fn temp_storage(&self) -> Option<&TempStorage> { + self.temp_storage.as_ref() + } + + /// Store data in temp storage. + #[cfg(feature = "fs")] + pub fn store_temp(&self, name: &str, data: &[u8]) -> AgentResult { + let storage = self + .temp_storage + .as_ref() + .ok_or(AgentError::NotConfigured("temp storage"))?; + storage.store_bytes(name, data).map_err(AgentError::Io) + } + + /// Store JSON in temp storage. + #[cfg(feature = "fs")] + pub fn store_temp_json( + &self, + name: &str, + data: &serde_json::Value, + ) -> AgentResult { + let storage = self + .temp_storage + .as_ref() + .ok_or(AgentError::NotConfigured("temp storage"))?; + storage.store_json(name, data).map_err(AgentError::Io) + } + + /// Read data from temp storage. + #[cfg(feature = "fs")] + pub fn read_temp(&self, name: &str) -> AgentResult> { + let storage = self + .temp_storage + .as_ref() + .ok_or(AgentError::NotConfigured("temp storage"))?; + storage.read_bytes(name).map_err(AgentError::Io) + } + + /// Read JSON from temp storage. + #[cfg(feature = "fs")] + pub fn read_temp_json(&self, name: &str) -> AgentResult { + let storage = self + .temp_storage + .as_ref() + .ok_or(AgentError::NotConfigured("temp storage"))?; + storage.read_json(name).map_err(AgentError::Io) + } + + // ==================== Helper Methods ==================== + + /// Clean HTML by removing scripts, styles, etc. + fn clean_html(&self, html: &str) -> String { + use crate::config::HtmlCleaningMode; + + match self.config.html_cleaning { + HtmlCleaningMode::Raw => html.to_string(), + HtmlCleaningMode::Minimal => { + // Remove scripts only + remove_tags(html, &["script"]) + } + HtmlCleaningMode::Default => { + // Remove scripts, styles, comments + remove_tags(html, &["script", "style", "noscript"]) + } + HtmlCleaningMode::Aggressive => { + // Remove more elements + remove_tags( + html, + &[ + "script", "style", "noscript", "svg", "canvas", "video", "audio", "iframe", + ], + ) + } + } + } + + /// Truncate HTML to max bytes. + fn truncate_html<'a>(&self, html: &'a str) -> &'a str { + if html.len() <= self.config.html_max_bytes { + html + } else { + // Find a safe break point + let truncated = &html[..self.config.html_max_bytes]; + // Try to break at a tag boundary + if let Some(pos) = truncated.rfind('<') { + &truncated[..pos] + } else { + truncated + } + } + } + + /// Parse JSON from LLM response. + fn parse_json(&self, content: &str) -> AgentResult { + // Try direct parse first + if let Ok(json) = serde_json::from_str(content) { + return Ok(json); + } + + // Try to extract JSON from markdown code block + if let Some(start) = content.find("```json") { + let start = start + 7; + if let Some(end) = content[start..].find("```") { + let json_str = content[start..start + end].trim(); + if let Ok(json) = serde_json::from_str(json_str) { + return Ok(json); + } + } + } + + // Try to extract JSON from plain code block + if let Some(start) = content.find("```") { + let start = start + 3; + // Skip language identifier if present + let start = content[start..] + .find('\n') + .map(|n| start + n + 1) + .unwrap_or(start); + if let Some(end) = content[start..].find("```") { + let json_str = content[start..start + end].trim(); + if let Ok(json) = serde_json::from_str(json_str) { + return Ok(json); + } + } + } + + // Try to find JSON object in content + if let Some(start) = content.find('{') { + if let Some(end) = content.rfind('}') { + let json_str = &content[start..=end]; + if let Ok(json) = serde_json::from_str(json_str) { + return Ok(json); + } + } + } + + Err(AgentError::Json( + serde_json::from_str::(content).unwrap_err(), + )) + } +} + +/// Remove specified HTML tags (and their content) from HTML. +/// +/// Single-pass per tag — avoids O(n²) from repeated `find()` + concatenation. +fn remove_tags(html: &str, tags: &[&str]) -> String { + let mut result = html.to_string(); + for tag in tags { + let open = format!("<{}", tag); + let close = format!("", tag); + let mut out = String::with_capacity(result.len()); + let lower = result.to_lowercase(); + let mut pos = 0; + while pos < result.len() { + if let Some(rel_start) = lower[pos..].find(&open) { + let start = pos + rel_start; + out.push_str(&result[pos..start]); + if let Some(rel_end) = lower[start..].find(&close) { + pos = start + rel_end + close.len(); + } else { + // No closing tag — keep remaining text as-is + out.push_str(&result[start..]); + pos = result.len(); + } + } else { + out.push_str(&result[pos..]); + break; + } + } + result = out; + } + result +} + +/// Result from fetching a URL. +#[derive(Debug, Clone)] +pub struct FetchResult { + /// The URL that was fetched. + pub url: String, + /// HTTP status code. + pub status: u16, + /// Content type header. + pub content_type: String, + /// HTML content. + pub html: String, +} + +/// Result from research. +#[cfg(feature = "search")] +#[derive(Debug, Clone)] +pub struct ResearchResult { + /// The original research topic. + pub topic: String, + /// Search results used. + pub search_results: SearchResults, + /// Extracted data from each page. + pub extractions: Vec, + /// Synthesized summary. + pub summary: Option, + /// Token usage. + pub usage: TokenUsage, +} + +/// Extraction from a single page. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PageExtraction { + /// Page URL. + pub url: String, + /// Page title. + pub title: String, + /// Extracted data. + pub extracted: serde_json::Value, +} + +/// Agent builder for configuring and creating agents. +pub struct AgentBuilder { + config: AgentConfig, + llm: Option>, + spider_cloud: Option, + proxies: Option>, + client: Option, + #[cfg(feature = "search")] + search_provider: Option>, + #[cfg(feature = "chrome")] + browser: Option, + #[cfg(feature = "webdriver")] + webdriver: Option, + #[cfg(feature = "fs")] + enable_temp_storage: bool, +} + +impl AgentBuilder { + /// Create a new builder with defaults. + pub fn new() -> Self { + Self { + config: AgentConfig::default(), + llm: None, + spider_cloud: None, + proxies: None, + client: None, + #[cfg(feature = "search")] + search_provider: None, + #[cfg(feature = "chrome")] + browser: None, + #[cfg(feature = "webdriver")] + webdriver: None, + #[cfg(feature = "fs")] + enable_temp_storage: false, + } + } + + /// Set the agent configuration. + pub fn with_config(mut self, config: AgentConfig) -> Self { + self.config = config; + self + } + + /// Set the system prompt. + pub fn with_system_prompt(mut self, prompt: impl Into) -> Self { + self.config.system_prompt = Some(prompt.into()); + self + } + + /// Set max concurrent LLM calls. + pub fn with_max_concurrent_llm_calls(mut self, n: usize) -> Self { + self.config.max_concurrent_llm_calls = n; + self + } + + /// Configure with OpenAI provider. + #[cfg(feature = "openai")] + pub fn with_openai(mut self, api_key: impl Into, model: impl Into) -> Self { + self.llm = Some(Box::new(crate::llm::OpenAIProvider::new(api_key, model))); + self + } + + /// Configure with OpenAI-compatible provider. + #[cfg(feature = "openai")] + pub fn with_openai_compatible( + mut self, + api_url: impl Into, + api_key: impl Into, + model: impl Into, + ) -> Self { + self.llm = Some(Box::new( + crate::llm::OpenAIProvider::new(api_key, model).with_api_url(api_url), + )); + self + } + + /// Configure with OpenAI Responses API. + /// + /// Uses the stateful Responses API (`/v1/responses`) instead of Chat + /// Completions. System messages become `instructions`, user/assistant + /// messages become `input` items. + #[cfg(feature = "openai")] + pub fn with_openai_responses( + mut self, + api_key: impl Into, + model: impl Into, + ) -> Self { + self.llm = Some(Box::new( + crate::llm::OpenAIProvider::new(api_key, model).with_responses_api(), + )); + self + } + + /// Configure with OpenAI-compatible provider using the Responses API. + #[cfg(feature = "openai")] + pub fn with_openai_compatible_responses( + mut self, + api_url: impl Into, + api_key: impl Into, + model: impl Into, + ) -> Self { + self.llm = Some(Box::new( + crate::llm::OpenAIProvider::new(api_key, model) + .with_responses_api() + .with_api_url(api_url), + )); + self + } + + /// Register Spider Cloud tools using an API key. + /// + /// Registers `/crawl`, `/scrape`, `/search`, `/links`, `/transform`, and + /// `/unblocker`. + /// AI routes remain disabled unless enabled in `with_spider_cloud_config`. + pub fn with_spider_cloud(mut self, api_key: impl Into) -> Self { + self.spider_cloud = Some(SpiderCloudToolConfig::new(api_key)); + self + } + + /// Register Spider Cloud tools using a full config. + /// + /// Use this when you need custom API URL, route toggles, or AI route gating. + pub fn with_spider_cloud_config(mut self, config: SpiderCloudToolConfig) -> Self { + self.spider_cloud = Some(config); + self + } + + /// Set the HTTP request timeout. + /// + /// Pass `None` for no timeout (infinite). Defaults to 60 seconds. + /// + /// # Example + /// ```ignore + /// use std::time::Duration; + /// let agent = Agent::builder() + /// .with_timeout(Some(Duration::from_secs(300))) + /// .build()?; + /// ``` + pub fn with_timeout(mut self, timeout: Option) -> Self { + match timeout { + Some(d) => self.config.timeout = d, + None => self.config.timeout = std::time::Duration::MAX, + } + self + } + + /// Provide a pre-built [`reqwest::Client`]. + /// + /// When set, the builder skips its own client construction (timeout, + /// proxy settings, etc.) and uses this client directly. This gives + /// full control over TLS, timeouts, connection pools, and proxies. + /// + /// # Example + /// ```ignore + /// let client = reqwest::Client::builder() + /// .timeout(std::time::Duration::from_secs(120)) + /// .proxy(reqwest::Proxy::all("http://proxy:8080")?) + /// .build()?; + /// + /// let agent = Agent::builder() + /// .with_client(client) + /// .with_openai("sk-...", "gpt-4o") + /// .build()?; + /// ``` + pub fn with_client(mut self, client: reqwest::Client) -> Self { + self.client = Some(client); + self + } + + /// Configure one or more HTTP/SOCKS proxies. + /// + /// Each entry is a proxy URL (e.g. `http://host:port`, `socks5://host:port`). + /// The first proxy is applied to the underlying HTTP client; additional + /// proxies are added via `reqwest::Proxy::all`. + /// + /// # Example + /// ```ignore + /// let agent = Agent::builder() + /// .with_openai("sk-...", "gpt-4o") + /// .with_proxies(vec!["http://proxy.example.com:8080".into()]) + /// .build()?; + /// ``` + pub fn with_proxies(mut self, proxies: Vec) -> Self { + if !proxies.is_empty() { + self.proxies = Some(proxies); + } + self + } + + /// Configure a single HTTP/SOCKS proxy. + pub fn with_proxy(mut self, proxy: impl Into) -> Self { + self.proxies = Some(vec![proxy.into()]); + self + } + + /// Configure with Serper search provider. + #[cfg(feature = "search_serper")] + pub fn with_search_serper(mut self, api_key: impl Into) -> Self { + self.search_provider = Some(Box::new(crate::search::SerperProvider::new(api_key))); + self + } + + /// Configure with Brave search provider. + #[cfg(feature = "search_brave")] + pub fn with_search_brave(mut self, api_key: impl Into) -> Self { + self.search_provider = Some(Box::new(crate::search::BraveProvider::new(api_key))); + self + } + + /// Configure with Bing search provider. + #[cfg(feature = "search_bing")] + pub fn with_search_bing(mut self, api_key: impl Into) -> Self { + self.search_provider = Some(Box::new(crate::search::BingProvider::new(api_key))); + self + } + + /// Configure with Tavily search provider. + #[cfg(feature = "search_tavily")] + pub fn with_search_tavily(mut self, api_key: impl Into) -> Self { + self.search_provider = Some(Box::new(crate::search::TavilyProvider::new(api_key))); + self + } + + /// Configure with a browser context for Chrome automation. + #[cfg(feature = "chrome")] + pub fn with_browser(mut self, browser: BrowserContext) -> Self { + self.browser = Some(browser); + self + } + + /// Configure with a browser from existing browser and page. + #[cfg(feature = "chrome")] + pub fn with_browser_page( + mut self, + browser: std::sync::Arc, + page: std::sync::Arc, + ) -> Self { + self.browser = Some(BrowserContext::new(browser, page)); + self + } + + /// Enable temporary filesystem storage for large operations. + #[cfg(feature = "fs")] + pub fn with_temp_storage(mut self) -> Self { + self.enable_temp_storage = true; + self + } + + /// Configure with a WebDriver context. + #[cfg(feature = "webdriver")] + pub fn with_webdriver(mut self, webdriver: WebDriverContext) -> Self { + self.webdriver = Some(webdriver); + self + } + + /// Configure with a WebDriver from existing driver. + #[cfg(feature = "webdriver")] + pub fn with_webdriver_driver( + mut self, + driver: std::sync::Arc, + ) -> Self { + self.webdriver = Some(WebDriverContext::new(driver)); + self + } + + /// Build the agent. + pub fn build(self) -> AgentResult { + let client = if let Some(client) = self.client { + client + } else { + let mut builder = reqwest::Client::builder(); + + // Duration::MAX effectively disables the timeout. + if self.config.timeout != std::time::Duration::MAX { + builder = builder.timeout(self.config.timeout); + } + + if let Some(proxies) = &self.proxies { + for proxy_url in proxies { + let proxy = reqwest::Proxy::all(proxy_url).map_err(AgentError::Http)?; + builder = builder.proxy(proxy); + } + } + + builder.build().map_err(AgentError::Http)? + }; + + let semaphore = Arc::new(Semaphore::new(self.config.max_concurrent_llm_calls)); + + #[cfg(feature = "fs")] + let temp_storage = if self.enable_temp_storage { + Some(TempStorage::new().map_err(AgentError::Io)?) + } else { + None + }; + + let custom_tools = CustomToolRegistry::new(); + if let Some(cfg) = self.spider_cloud.as_ref() { + custom_tools.register_spider_cloud(cfg); + } + + Ok(Agent { + llm: self.llm, + client, + #[cfg(feature = "search")] + search_provider: self.search_provider, + #[cfg(feature = "chrome")] + browser: self.browser, + #[cfg(feature = "webdriver")] + webdriver: self.webdriver, + #[cfg(feature = "fs")] + temp_storage, + memory: AgentMemory::new(), + llm_semaphore: semaphore, + config: self.config, + usage: Arc::new(UsageStats::new()), + custom_tools, + }) + } +} + +impl Default for AgentBuilder { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_builder_registers_spider_cloud_default_routes() { + let agent = Agent::builder() + .with_spider_cloud("sk_spider_cloud") + .build() + .expect("agent should build"); + + let tools = agent.list_custom_tools(); + assert!(tools.contains(&"spider_cloud_crawl".to_string())); + assert!(tools.contains(&"spider_cloud_scrape".to_string())); + assert!(tools.contains(&"spider_cloud_search".to_string())); + assert!(tools.contains(&"spider_cloud_links".to_string())); + assert!(tools.contains(&"spider_cloud_transform".to_string())); + assert!(tools.contains(&"spider_cloud_unblocker".to_string())); + assert!(!tools.contains(&"spider_cloud_ai_scrape".to_string())); + } + + #[test] + fn test_builder_registers_spider_cloud_ai_routes_when_enabled() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud").with_enable_ai_routes(true); + let agent = Agent::builder() + .with_spider_cloud_config(cfg) + .build() + .expect("agent should build"); + + let tools = agent.list_custom_tools(); + assert!(tools.contains(&"spider_cloud_ai_crawl".to_string())); + assert!(tools.contains(&"spider_cloud_ai_scrape".to_string())); + assert!(tools.contains(&"spider_cloud_ai_search".to_string())); + assert!(tools.contains(&"spider_cloud_ai_browser".to_string())); + assert!(tools.contains(&"spider_cloud_ai_links".to_string())); + } + + #[test] + fn test_builder_with_single_proxy() { + let agent = Agent::builder() + .with_proxy("http://proxy.example.com:8080") + .build() + .expect("agent with proxy should build"); + // Client was constructed — no panic, proxy applied at reqwest level. + drop(agent); + } + + #[test] + fn test_builder_with_multiple_proxies() { + let agent = Agent::builder() + .with_proxies(vec![ + "http://proxy1.example.com:8080".into(), + "http://proxy2.example.com:9090".into(), + ]) + .build() + .expect("agent with multiple proxies should build"); + drop(agent); + } + + #[test] + fn test_builder_with_socks5_proxy() { + let agent = Agent::builder() + .with_proxy("socks5://127.0.0.1:1080") + .build() + .expect("agent with socks5 proxy should build"); + drop(agent); + } + + #[test] + fn test_builder_with_empty_proxies_no_op() { + let agent = Agent::builder() + .with_proxies(vec![]) + .build() + .expect("agent with empty proxies should build"); + drop(agent); + } + + #[test] + fn test_builder_with_invalid_proxy_returns_error() { + let result = Agent::builder() + .with_proxy("not a valid url at all ://") + .build(); + assert!(result.is_err(), "invalid proxy URL should fail at build"); + } + + #[test] + fn test_builder_no_proxies_by_default() { + // Default builder has no proxies — just ensure it builds fine. + let agent = Agent::builder() + .build() + .expect("default agent should build"); + drop(agent); + } + + #[test] + fn test_builder_with_custom_client() { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .build() + .expect("custom client should build"); + + let agent = Agent::builder() + .with_client(client) + .build() + .expect("agent with custom client should build"); + // Verify the client accessor works. + let _ = agent.client(); + } + + #[test] + fn test_builder_with_custom_client_ignores_proxy_and_timeout() { + // When a custom client is provided, proxy/timeout settings on the + // builder are bypassed — the caller owns the full client config. + let client = reqwest::Client::new(); + let agent = Agent::builder() + .with_client(client) + .with_proxy("http://proxy.example.com:8080") + .with_timeout(Some(std::time::Duration::from_secs(999))) + .build() + .expect("custom client should take precedence"); + drop(agent); + } + + #[test] + fn test_builder_with_timeout_some() { + let agent = Agent::builder() + .with_timeout(Some(std::time::Duration::from_secs(300))) + .build() + .expect("agent with 300s timeout should build"); + drop(agent); + } + + #[test] + fn test_builder_with_timeout_none_infinite() { + let agent = Agent::builder() + .with_timeout(None) + .build() + .expect("agent with no timeout should build"); + drop(agent); + } + + #[test] + fn test_client_accessor_returns_reference() { + let agent = Agent::builder().build().expect("agent should build"); + // Ensure we can obtain a shared reference without moving. + let _c1 = agent.client(); + let _c2 = agent.client(); + } +} diff --git a/spider_agent/src/automation/browser.rs b/spider_agent/src/automation/browser.rs new file mode 100644 index 000000000..9322da30c --- /dev/null +++ b/spider_agent/src/automation/browser.rs @@ -0,0 +1,6481 @@ +//! Browser-specific automation methods for chromiumoxide integration. +//! +//! This module provides browser automation capabilities that require a +//! Chrome browser page. All methods are gated behind `#[cfg(feature = "chrome")]`. + +#[cfg(feature = "chrome")] +use base64::{engine::general_purpose, Engine as _}; +#[cfg(feature = "chrome")] +use chromiumoxide::{ + cdp::browser_protocol::page::CaptureScreenshotFormat, layout::Point, page::ScreenshotParams, + Page, +}; + +use super::{ + clean_html_with_profile, parse_tool_calls, tool_calls_to_steps, truncate_utf8_tail, ActResult, + ActionToolSchemas, AutomationMemory, AutomationResult, AutomationUsage, CaptureProfile, + EngineError, EngineResult, HtmlCleaningProfile, MemoryOperation, PageObservation, + RemoteMultimodalConfig, RemoteMultimodalEngine, +}; + +/// State signature for stagnation detection. +#[cfg(feature = "chrome")] +#[derive(Debug, Clone)] +pub(crate) struct StateSignature { + /// Current page URL. + url: String, + /// Current document title. + title: String, + /// Hash of the last N bytes of cleaned HTML (tail hash). + html_tail_hash: u64, + /// Total cleaned HTML length. + html_len: usize, +} + +#[cfg(feature = "chrome")] +impl StateSignature { + pub fn new(url: &str, title: &str, html: &str) -> Self { + use super::fnv1a64; + let tail = truncate_utf8_tail(html, 2048); + let h = fnv1a64(tail.as_bytes()); + Self { + url: url.to_string(), + title: title.to_string(), + html_tail_hash: h, + html_len: html.len(), + } + } + + pub fn eq_soft(&self, other: &Self) -> bool { + self.url == other.url + && self.title == other.title + && self.html_tail_hash == other.html_tail_hash + && self.html_len == other.html_len + } +} + +/// Internal plan from LLM response. +#[cfg(feature = "chrome")] +#[derive(Debug, Clone, Default)] +pub(crate) struct AutomationPlan { + pub label: String, + pub done: bool, + pub steps: Vec, + pub extracted: Option, + pub memory_ops: Vec, + pub usage: AutomationUsage, + pub relevant: Option, + pub reasoning: Option, +} + +/// Outcome of a single action execution, providing feedback for the LLM. +#[cfg(feature = "chrome")] +#[derive(Debug, Clone)] +pub(crate) struct ActionOutcome { + /// The action name (e.g. "Click", "Fill", "Evaluate"). + pub action: String, + /// Whether the action succeeded. + pub success: bool, + /// Human-readable detail when the action failed (e.g. "selector not found"). + pub error: Option, +} + +#[cfg(feature = "chrome")] +impl ActionOutcome { + /// Create a successful outcome. + pub fn ok(action: impl Into) -> Self { + Self { + action: action.into(), + success: true, + error: None, + } + } + + /// Create a failed outcome with a reason. + pub fn fail(action: impl Into, reason: impl Into) -> Self { + Self { + action: action.into(), + success: false, + error: Some(reason.into()), + } + } + + /// Format as a compact single-line summary for prompt injection. + pub fn to_feedback_line(&self) -> String { + if self.success { + format!("- {} → ok", self.action) + } else { + format!( + "- {} → FAILED: {}", + self.action, + self.error.as_deref().unwrap_or("unknown error") + ) + } + } +} + +/// Format a slice of action outcomes into a prompt section. +/// Returns `None` if there are no outcomes or all succeeded (to avoid noise). +#[cfg(feature = "chrome")] +pub(crate) fn format_action_feedback(outcomes: &[ActionOutcome]) -> Option { + if outcomes.is_empty() { + return None; + } + let has_failure = outcomes.iter().any(|o| !o.success); + if !has_failure { + return None; + } + let mut out = String::from("PREVIOUS ACTION RESULTS:\n"); + for o in outcomes { + out.push_str(&o.to_feedback_line()); + out.push('\n'); + } + out.push('\n'); + Some(out) +} + +/// Default timeout for internal `page.evaluate()` calls (scroll, keyboard, etc). +#[cfg(feature = "chrome")] +const EVAL_TIMEOUT_SECS: u64 = 10; + +/// Maximum iterations for `InfiniteScroll` to prevent LLM-controlled unbounded loops. +#[cfg(feature = "chrome")] +const MAX_INFINITE_SCROLL_ITERATIONS: u64 = 100; + +/// Maximum timeout for `WaitForWithTimeout` action (30 seconds). +#[cfg(feature = "chrome")] +const MAX_WAIT_FOR_TIMEOUT_MS: u64 = 30_000; + +/// Default concurrency limit for `run_spawn_pages_*` functions. +#[cfg(feature = "chrome")] +const DEFAULT_SPAWN_CONCURRENCY: usize = 5; + +/// Maximum hold duration for `ClickHold`/`ClickHoldPoint` (30 seconds). +#[cfg(feature = "chrome")] +const MAX_HOLD_MS: u64 = 30_000; + +/// Maximum URLs a single `OpenPage` action can spawn. +#[cfg(feature = "chrome")] +const MAX_SPAWN_PAGES_PER_ACTION: usize = 20; + +/// Maximum viewport dimension (8K resolution cap). +#[cfg(feature = "chrome")] +const MAX_VIEWPORT_DIM: i64 = 7680; + +/// Execute a `page.evaluate()` call with a timeout guard. +/// +/// Returns `Ok(())` on success, `Err(reason)` on JS error or timeout. +/// This is a free function to avoid borrowing issues with `&self`. +#[cfg(feature = "chrome")] +async fn eval_with_timeout( + page: &Page, + js: impl Into, + timeout_secs: u64, +) -> Result<(), String> { + let js = js.into(); + match tokio::time::timeout( + std::time::Duration::from_secs(timeout_secs), + page.evaluate(js), + ) + .await + { + Ok(Ok(_)) => Ok(()), + Ok(Err(e)) => Err(e.to_string()), + Err(_) => Err(format!("JS execution timed out after {timeout_secs}s")), + } +} + +/// Escape a string for safe interpolation into JavaScript source code. +/// +/// Uses `serde_json::to_string` which produces a JSON-encoded `"string"` with +/// proper escaping of quotes, backslashes, newlines, etc. The result includes +/// surrounding double-quotes, suitable for direct use in JS expressions like +/// `document.querySelector(js_escape(sel))`. +#[cfg(feature = "chrome")] +fn js_escape(s: &str) -> String { + serde_json::to_string(s).unwrap_or_default() +} + +#[cfg(feature = "chrome")] +impl RemoteMultimodalEngine { + // ----------------------------------------------------------------- + // Capture helpers + // ----------------------------------------------------------------- + + /// Whether this round should include screenshot capture/input. + /// + /// This enforces text-only behavior when the resolved round model does not + /// support vision, while still honoring explicit `include_screenshot` overrides. + #[inline] + fn should_include_screenshot_for_round( + &self, + effective_cfg: &RemoteMultimodalConfig, + use_vision: bool, + ) -> bool { + if !use_vision { + return false; + } + match effective_cfg.include_screenshot { + Some(explicit) => explicit, + None => { + let (_, model_name, _) = self.resolve_model_for_round(use_vision); + super::supports_vision(model_name) + } + } + } + + /// Whether the resolved "vision route" model can actually process image input. + #[inline] + fn has_vision_capable_route(&self) -> bool { + let (_, model_name, _) = self.resolve_model_for_round(true); + super::supports_vision(model_name) + } + + /// Apply a text-only flavor to the system prompt for rounds where no image + /// is provided. This keeps action bindings intact while removing screenshot + /// expectations that can mislead text-only models. + #[inline] + fn apply_text_only_prompt_flavor(system_msg: &mut String, can_request_vision: bool) { + const SCREENSHOT_INPUT_LINE: &str = + "- Screenshot of current page state (may be omitted in text-only rounds)\n"; + const TEXT_ONLY_INPUT_LINE: &str = + "- No screenshot is provided this round; use URL/title/HTML context.\n"; + + if system_msg.contains(SCREENSHOT_INPUT_LINE) { + *system_msg = system_msg.replacen(SCREENSHOT_INPUT_LINE, TEXT_ONLY_INPUT_LINE, 1); + } + + if system_msg.contains("→ visible in screenshot") { + *system_msg = + system_msg.replace("→ visible in screenshot", "→ visible in next round context"); + } + + if can_request_vision { + system_msg.push_str("\n\n---\nMODE: TEXT-ONLY (no screenshot this round). Use HTML context and memory to decide actions. If you need visual information, set `{\"op\":\"set\",\"key\":\"request_vision\",\"value\":true}` in memory_ops to receive a screenshot next round.\n"); + } else { + system_msg.push_str( + "\n\n---\nMODE: TEXT-ONLY (no screenshot available for this model/config). Use HTML context and memory only.\n", + ); + } + } + + /// Capture screenshot as data URL with profile settings. + /// Automatically applies grayscale filter for text CAPTCHAs to improve readability. + pub(crate) async fn screenshot_as_data_url_with_profile( + &self, + page: &Page, + cap: &CaptureProfile, + ) -> EngineResult { + // Auto-detect text CAPTCHA and apply grayscale for better readability + // Only apply if input is empty (first view) - don't grayscale after typing + let _ = page + .evaluate( + r#" + (() => { + const text = (document.body?.innerText || '').toLowerCase(); + const input = document.querySelector('input[type="text"], input:not([type])'); + const inputEmpty = input && !input.value; + const hasTextCaptcha = inputEmpty && ( + text.includes('enter the text') || + text.includes('enter the') || + text.includes('type the') || + text.includes('wiggles') || + text.includes('level 3') || + text.includes('below') + ); + if (hasTextCaptcha) { + // Grayscale + high contrast to make text clearer + const filter = 'grayscale(100%) contrast(150%)'; + document.documentElement.style.filter = filter; + document.querySelectorAll('canvas, img, svg, div').forEach(el => { + el.style.filter = filter; + }); + } + })() + "#, + ) + .await; + + let params = ScreenshotParams::builder() + .format(CaptureScreenshotFormat::Png) + .full_page(cap.full_page) + .omit_background(cap.omit_background) + .build(); + + let png = + match tokio::time::timeout(std::time::Duration::from_secs(15), page.screenshot(params)) + .await + { + Ok(Ok(data)) => data, + Ok(Err(e)) => { + return Err(EngineError::Remote(format!("screenshot failed: {e}"))); + } + Err(_) => { + log::warn!("Screenshot timed out after 15s"); + return Err(EngineError::Remote("screenshot timed out after 15s".into())); + } + }; + + // Restore color after screenshot + let _ = page + .evaluate( + r#" + document.documentElement.style.filter = ''; + document.body.style.filter = ''; + document.querySelectorAll('canvas, img, svg, div').forEach(el => { + el.style.filter = ''; + }); + "#, + ) + .await; + + let b64 = general_purpose::STANDARD.encode(png); + Ok(format!("data:image/png;base64,{}", b64)) + } + + /// Take a final screenshot and return as base64 string. + pub(crate) async fn take_final_screenshot(&self, page: &Page) -> EngineResult { + let params = ScreenshotParams::builder() + .format(CaptureScreenshotFormat::Png) + .full_page(true) + .omit_background(true) + .build(); + + let png = + match tokio::time::timeout(std::time::Duration::from_secs(15), page.screenshot(params)) + .await + { + Ok(Ok(data)) => data, + Ok(Err(e)) => { + return Err(EngineError::Remote(format!("screenshot failed: {e}"))); + } + Err(_) => { + log::warn!("Final screenshot timed out after 15s"); + return Err(EngineError::Remote( + "final screenshot timed out after 15s".into(), + )); + } + }; + + Ok(general_purpose::STANDARD.encode(png)) + } + + /// Get page title context. + pub(crate) async fn title_context( + &self, + page: &Page, + effective_cfg: &RemoteMultimodalConfig, + ) -> String { + if !effective_cfg.include_title { + return String::new(); + } + match page.get_title().await { + Ok(t) => t.unwrap_or_default(), + Err(_) => String::new(), + } + } + + /// Get page URL context. + pub(crate) async fn url_context( + &self, + page: &Page, + effective_cfg: &RemoteMultimodalConfig, + ) -> String { + if !effective_cfg.include_url { + return String::new(); + } + match page.url().await { + Ok(u_opt) => u_opt.unwrap_or_default(), + Err(_) => String::new(), + } + } + + /// Get cleaned HTML context with profile settings. + pub(crate) async fn html_context_with_profile( + &self, + page: &Page, + effective_cfg: &RemoteMultimodalConfig, + cap: &CaptureProfile, + ) -> EngineResult { + if !effective_cfg.include_html { + return Ok(String::new()); + } + + let raw_html = page + .content() + .await + .map_err(|e| EngineError::Remote(format!("page.content() failed: {e}")))?; + + let cleaned = clean_html_with_profile(&raw_html, cap.html_cleaning); + + // effective max = min(profile, cfg) + let max = cap.html_max_bytes.min(effective_cfg.html_max_bytes); + Ok(truncate_utf8_tail(&cleaned, max)) + } + + /// Build the user prompt for a round using captured state. + #[allow(clippy::too_many_arguments)] + pub(crate) fn build_user_prompt( + &self, + effective_cfg: &RemoteMultimodalConfig, + _cap: &CaptureProfile, + url_input: &str, + url_now: &str, + title_now: &str, + html: &str, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + loop_blocklist: &[String], + memory: Option<&AutomationMemory>, + user_message_extra: Option<&str>, + ) -> String { + let mut out = String::with_capacity( + 256 + url_input.len() + + url_now.len() + + title_now.len() + + html.len().min(effective_cfg.html_max_bytes), + ); + + out.push_str("RUN CONTEXT:\n"); + out.push_str("- url_input: "); + out.push_str(url_input); + out.push('\n'); + out.push_str("- round: "); + out.push_str(&(round_idx + 1).to_string()); + out.push('\n'); + out.push_str("- stagnated: "); + out.push_str(if stagnated { "true" } else { "false" }); + out.push_str("\n\n"); + + // Include memory context if available and non-empty + if let Some(mem) = memory { + if !mem.is_empty() { + let mem_ctx = mem.to_context_string(); + if !mem_ctx.is_empty() { + out.push_str("SESSION MEMORY:\n"); + out.push_str(&mem_ctx); + out.push_str("\n\n"); + } + } + } + + if effective_cfg.include_url && !url_now.is_empty() { + out.push_str("CURRENT URL:\n"); + out.push_str(url_now); + out.push_str("\n\n"); + } + + if effective_cfg.include_title && !title_now.is_empty() { + out.push_str("PAGE TITLE:\n"); + out.push_str(title_now); + out.push_str("\n\n"); + } + + if effective_cfg.include_html && !html.is_empty() { + out.push_str("HTML CONTEXT:\n"); + out.push_str(html); + out.push_str("\n\n"); + } + + // Include user instructions if provided + if let Some(extra) = user_message_extra { + if !extra.trim().is_empty() { + out.push_str("---\nUSER INSTRUCTIONS:\n"); + out.push_str(extra.trim()); + out.push_str("\n\n"); + } + } + + // Stuck-loop warning: inject when model repeats identical actions + if action_stuck_rounds >= 3 { + out.push_str("--- ACTION LOOP DETECTED ---\n"); + out.push_str(&format!( + "You have repeated the EXACT same actions {} consecutive rounds with ZERO progress.\n", + action_stuck_rounds + )); + out.push_str("Your current approach is NOT WORKING. You MUST change strategy NOW:\n"); + out.push_str("1. Use Evaluate to inspect DOM state - read element text, check CSS classes, find the actual button/element state\n"); + out.push_str("2. Try completely different selectors or use ClickPoint at exact pixel coordinates\n"); + out.push_str("3. If clicking a button repeatedly does nothing, the task may have FAILED - look for error messages, try refreshing the page\n"); + out.push_str("4. If you think you won/completed something but Verify does not advance, you likely LOST or the answer was wrong - retry\n"); + out.push_str("5. Do NOT repeat the same steps again. Your next response MUST contain different actions.\n\n"); + } + + if !loop_blocklist.is_empty() { + out.push_str("LOOP BLOCKLIST (DO NOT REPEAT THESE EXACT ACTIONS):\n"); + for blocked in loop_blocklist.iter().take(10) { + out.push_str("- "); + out.push_str(blocked); + out.push('\n'); + } + out.push_str( + "Use different selectors/coordinates, or switch interaction method entirely.\n\n", + ); + } + + if effective_cfg.is_extraction_only() { + out.push_str( + "TASK:\nExtract structured data from the page above. Return JSON with label, done: true, steps: [], and extracted data.\n", + ); + } else { + out.push_str( + "TASK:\nReturn the next automation steps as a single JSON object (no prose).\n", + ); + } + + out + } + + /// Build a compact blocklist summary for repeated action plans. + /// + /// This is fed back into the next prompt to discourage repeating exactly + /// the same actions after loop detection. + fn summarize_step_blocklist(steps: &[serde_json::Value], max_items: usize) -> Vec { + use std::collections::HashSet; + + let mut seen = HashSet::new(); + let mut out = Vec::new(); + + for step in steps { + let Some(obj) = step.as_object() else { + continue; + }; + + for (action, value) in obj { + let mut summary = String::from(action); + + if let Some(selector) = value.as_str() { + summary.push_str(": selector="); + summary.push_str(selector); + } else if let Some(selector) = value.get("selector").and_then(|v| v.as_str()) { + summary.push_str(": selector="); + summary.push_str(selector); + } else if let (Some(x), Some(y)) = ( + value.get("x").and_then(|v| v.as_f64()), + value.get("y").and_then(|v| v.as_f64()), + ) { + summary.push_str(": point=("); + summary.push_str(&format!("{x:.1}, {y:.1}")); + summary.push(')'); + } else { + let rendered = truncate_utf8_tail(&value.to_string(), 100); + if !rendered.is_empty() { + summary.push_str(": "); + summary.push_str(&rendered); + } + } + + if seen.insert(summary.clone()) { + out.push(summary); + if out.len() >= max_items { + return out; + } + } + } + } + + out + } + + /// Extract a stable level key from model `extracted` payload. + /// + /// Expected fields (when present): `current_level` and `level_name`. + fn extracted_level_key(extracted: Option<&serde_json::Value>) -> Option { + let extracted = extracted?.as_object()?; + let level_num = extracted.get("current_level").and_then(|v| v.as_u64()); + let level_name = extracted + .get("level_name") + .and_then(|v| v.as_str()) + .map(str::trim) + .filter(|s| !s.is_empty()); + + if level_num.is_none() && level_name.is_none() { + return None; + } + + let mut key = String::new(); + if let Some(n) = level_num { + key.push('L'); + key.push_str(&n.to_string()); + } else { + key.push_str("L?"); + } + + if let Some(name) = level_name { + let normalized = name + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() { + c.to_ascii_lowercase() + } else { + '-' + } + }) + .collect::(); + key.push(':'); + key.push_str(normalized.trim_matches('-')); + } + + Some(key) + } + + // ----------------------------------------------------------------- + // Main automation loop + // ----------------------------------------------------------------- + + /// Runs iterative automation until the model declares completion or `max_rounds` is reached. + /// + /// Contract: + /// - Each round captures screenshot + (optional) URL/title/HTML + /// - Model returns a JSON object: + /// `{ "label": "...", "done": true|false, "steps": [ ... ] }` + /// - If `done == true` OR `steps` empty => stop successfully. + /// - Otherwise execute steps and loop again. + /// + /// URL gating: + /// - If `prompt_url_gate` is set, automation only runs for matched URLs. + /// - A match may supply an override config (merged onto base config). + pub async fn run(&self, page: &Page, url_input: &str) -> EngineResult { + self.run_with_memory(page, url_input, None).await + } + + /// Runs iterative automation with session memory for agentic workflows. + /// + /// Same as `run()` but accepts a mutable memory reference that persists + /// data across automation rounds. The LLM can read from and write to + /// this memory using memory operations in its responses. + /// + /// # Arguments + /// * `page` - The Chrome page to automate + /// * `url_input` - The URL being processed + /// * `memory` - Optional mutable memory for session state + /// + /// # Memory Operations + /// The LLM can include a `memory_ops` array in its response: + /// ```json + /// { + /// "label": "...", + /// "done": false, + /// "steps": [...], + /// "memory_ops": [ + /// { "op": "set", "key": "user_id", "value": "12345" }, + /// { "op": "delete", "key": "temp" }, + /// { "op": "clear" } + /// ] + /// } + /// ``` + pub async fn run_with_memory( + &self, + page: &Page, + url_input: &str, + mut memory: Option<&mut AutomationMemory>, + ) -> EngineResult { + let mut effective_cfg: RemoteMultimodalConfig = self.cfg.clone(); + let mut effective_system_prompt = self.system_prompt.clone(); + let mut effective_system_prompt_extra = self.system_prompt_extra.clone(); + let mut effective_user_message_extra = self.user_message_extra.clone(); + + // 0) URL gating check + per-URL override application + if let Some(gate) = &self.prompt_url_gate { + let gate_match = gate.match_url(url_input); + if gate_match.is_none() { + return Ok(AutomationResult { + label: "url_not_allowed".into(), + steps_executed: 0, + success: true, + error: None, + usage: AutomationUsage::default(), + extracted: None, + screenshot: None, + spawn_pages: Vec::new(), + relevant: None, + reasoning: None, + }); + } + + if let Some(Some(override_cfg)) = gate_match { + let defaults = super::AutomationConfig::default(); + + if override_cfg.max_steps != defaults.max_steps { + effective_cfg.max_rounds = override_cfg.max_steps.max(1); + } + if override_cfg.max_retries != defaults.max_retries { + effective_cfg.retry.max_attempts = override_cfg.max_retries.max(1); + } + if override_cfg.capture_screenshots != defaults.capture_screenshots { + effective_cfg.screenshot = override_cfg.capture_screenshots; + } + if override_cfg.capture_profile != defaults.capture_profile { + effective_cfg.capture_profiles = vec![override_cfg.capture_profile.clone()]; + } + if override_cfg.extract_on_success || override_cfg.extraction_prompt.is_some() { + effective_cfg.extra_ai_data = true; + } + if let Some(extraction_prompt) = &override_cfg.extraction_prompt { + if !extraction_prompt.trim().is_empty() { + effective_cfg.extraction_prompt = Some(extraction_prompt.clone()); + } + } + + if let Some(system_prompt) = &override_cfg.system_prompt { + if !system_prompt.trim().is_empty() { + effective_system_prompt = Some(system_prompt.clone()); + } + } + if let Some(system_prompt_extra) = &override_cfg.system_prompt_extra { + if !system_prompt_extra.trim().is_empty() { + effective_system_prompt_extra = Some(system_prompt_extra.clone()); + } + } + if let Some(user_message_extra) = &override_cfg.user_message_extra { + if !user_message_extra.trim().is_empty() { + effective_user_message_extra = Some(user_message_extra.clone()); + } + } + } + } + + // Extraction-only optimization: skip screenshots unless explicitly requested. + // Saves ~35k tokens per call for vision-capable models doing text extraction. + let extraction_only = effective_cfg.is_extraction_only(); + let skip_screenshot_for_extraction = + extraction_only && effective_cfg.include_screenshot != Some(true); + + // capture profiles fallback + let capture_profiles: Vec = if effective_cfg.capture_profiles.is_empty() { + vec![ + CaptureProfile { + full_page: false, + omit_background: true, + html_cleaning: HtmlCleaningProfile::Default, + html_max_bytes: effective_cfg.html_max_bytes, + attempt_note: Some("default profile 1: viewport screenshot".into()), + ..Default::default() + }, + CaptureProfile { + full_page: true, + omit_background: true, + html_cleaning: HtmlCleaningProfile::Aggressive, + html_max_bytes: effective_cfg.html_max_bytes, + attempt_note: Some( + "default profile 2: full-page screenshot + aggressive HTML".into(), + ), + ..Default::default() + }, + ] + } else { + effective_cfg.capture_profiles.clone() + }; + + let mut total_steps_executed = 0usize; + let mut last_label = String::from("automation"); + let mut last_sig: Option = None; + let mut total_usage = AutomationUsage::default(); + let mut last_extracted: Option = None; + let mut last_relevant: Option = None; + let mut last_reasoning: Option = None; + let mut all_spawn_pages: Vec = Vec::new(); + // Stuck-loop detection: track hashed step sequences across rounds + let mut recent_step_hashes: std::collections::VecDeque = + std::collections::VecDeque::new(); + let mut action_stuck_rounds: usize = 0; + // Compact summary of repeated actions to block in the next round. + let mut loop_blocklist: Vec = Vec::new(); + // Dual-model routing: set by `request_vision` memory_op to force vision next round + let mut force_vision_next_round: bool = false; + // Action feedback from previous round (injected when failures occurred) + let mut last_action_feedback: Option = None; + + // Recall learned strategies from long-term experience memory. + // Uses spawn_blocking to contain !Send ffmpeg temporaries from memvid-rs. + #[cfg(feature = "memvid")] + if let Some(ref exp_mem) = self.experience_memory { + let exp_mem = exp_mem.clone(); + let query = format!("{} {}", url_input, last_label); + let handle = tokio::runtime::Handle::current(); + let recalled = tokio::task::spawn_blocking(move || { + handle.block_on(async move { + { + let mem = exp_mem.read().await; + mem.clear_cache(); + } + let mut mem = exp_mem.write().await; + let max_recall = mem.config.max_recall; + let max_context_chars = mem.config.max_context_chars; + match mem.recall(&query, max_recall).await { + Ok(experiences) if !experiences.is_empty() => { + let ctx = super::long_term_memory::ExperienceMemory::recall_to_context( + &experiences, + max_context_chars, + ); + if ctx.is_empty() { + None + } else { + log::info!( + "Recalled {} strategies ({} chars)", + experiences.len(), + ctx.len(), + ); + Some(ctx) + } + } + Ok(_) => None, + Err(e) => { + log::warn!("Failed to recall experiences: {}", e); + None + } + } + }) + }) + .await + .ok() + .flatten(); + + if let Some(learned_context) = recalled { + let existing = effective_system_prompt_extra.take().unwrap_or_default(); + effective_system_prompt_extra = Some(if existing.is_empty() { + learned_context + } else { + format!("{}\n\n{}", learned_context, existing) + }); + } + } + + // Chrome AI warm-up (when enabled or as last-resort fallback) + let use_chrome_ai = self.should_use_chrome_ai(); + if use_chrome_ai { + log::info!("Chrome AI mode: using in-page LanguageModel for inference"); + if let Err(e) = Self::warm_chrome_ai(page).await { + log::warn!("Chrome AI warm-up failed: {e}"); + } + } + + let default_cap = CaptureProfile::default(); + let rounds = effective_cfg.max_rounds.max(1); + for round_idx in 0..rounds { + let mut current_level_attempts: Option = None; + // pick capture profile by round (clamp to last) + let cap = capture_profiles + .get(round_idx) + .or_else(|| capture_profiles.last()) + .unwrap_or(&default_cap); + + // Dual-model routing decision (before capture) + let use_vision = self.should_use_vision_this_round( + round_idx, + last_sig.as_ref().map(|_| false).unwrap_or(false), // stagnation not yet known; re-checked below + action_stuck_rounds, + force_vision_next_round, + ); + // Reset per-round force flag + force_vision_next_round = false; + + // Capture state – skip screenshot when this round is text-only + let include_screenshot_this_round = use_vision + && !skip_screenshot_for_extraction + && self.should_include_screenshot_for_round(&effective_cfg, use_vision); + + let html_fut = self.html_context_with_profile(page, &effective_cfg, cap); + let url_fut = + async { Ok::(self.url_context(page, &effective_cfg).await) }; + let title_fut = + async { Ok::(self.title_context(page, &effective_cfg).await) }; + + #[allow(unused_mut)] + let (screenshot, html, mut url_now, mut title_now) = if include_screenshot_this_round { + let screenshot_fut = self.screenshot_as_data_url_with_profile(page, cap); + // Screenshot failures are non-fatal — continue without an image. + let (s_res, h, u, t) = tokio::join!(screenshot_fut, html_fut, url_fut, title_fut); + let h = h?; + let u = u?; + let t = t?; + let s = match s_res { + Ok(s) => s, + Err(e) => { + log::warn!("Screenshot failed (non-fatal), continuing text-only: {e}"); + String::new() + } + }; + (s, h, u, t) + } else { + let (h, u, t) = tokio::try_join!(html_fut, url_fut, title_fut)?; + (String::new(), h, u, t) + }; + + // Fallback to the input URL if page.url() is empty/unsupported. + if url_now.is_empty() { + url_now = url_input.to_string(); + } + + // quick stagnation heuristic + let sig = StateSignature::new(&url_now, &title_now, &html); + let stagnated = last_sig.as_ref().map(|p| p.eq_soft(&sig)).unwrap_or(false); + last_sig = Some(sig); + + // Run pre_evaluate JS from matching skills BEFORE the LLM sees the page. + // This lets the engine execute critical JS (board solvers, grid extractors) + // so the model only reads results from document.title and emits click actions. + #[cfg(feature = "skills")] + { + if let Some(ref registry) = self.skill_registry { + let pre_evals = registry.find_pre_evaluates(&url_now, &title_now, &html); + if !pre_evals.is_empty() { + for (skill_name, js) in &pre_evals { + log::info!( + "Running pre_evaluate for skill '{}' ({} bytes)", + skill_name, + js.len() + ); + if let Err(e) = eval_with_timeout(page, *js, EVAL_TIMEOUT_SECS).await { + log::warn!("pre_evaluate '{}' failed: {}", skill_name, e); + } + } + // Re-capture title after pre_evaluate (JS sets document.title) + let new_title = self.title_context(page, &effective_cfg).await; + if !new_title.is_empty() && new_title != title_now { + log::info!( + "Pre-evaluate updated title: '{}' -> '{}'", + &title_now[..title_now.len().min(80)], + &new_title[..new_title.len().min(80)] + ); + title_now = new_title.clone(); + + // TTT game loop: play entire game(s) before the LLM sees + // the page. Handles draws by waiting for auto-reset and + // replaying. Up to 3 games within one LLM round. + if new_title.starts_with("TTT:") { + let ttt_js: Option<&str> = pre_evals + .iter() + .find(|(name, _)| *name == "tic-tac-toe") + .map(|(_, js)| *js); + if let Some(ttt_js) = ttt_js { + let mut games_played = 0u32; + for _ttt_step in 0..30 { + // Parse current game state + let (my_win, th_win, full) = if let Some(json_str) = + title_now.strip_prefix("TTT:") + { + if let Ok(data) = + serde_json::from_str::(json_str) + { + ( + data.get("myWin") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + data.get("thWin") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + data.get("full") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + ) + } else { + (false, false, false) + } + } else { + break; + }; // title changed away from TTT + + if my_win || th_win { + log::info!( + "TTT game over: myWin={}, thWin={}", + my_win, + th_win + ); + if my_win { + // Engine clicks verify immediately — don't rely + // on the model since the game auto-resets quickly. + tokio::time::sleep( + std::time::Duration::from_millis(500), + ) + .await; + let _ = page + .click(chromiumoxide::layout::Point::new( + 0.0, 0.0, + )) + .await; // defocus + let verify_js = r#"(function(){ + var btn=document.querySelector('#captcha-verify-button,button.captcha-verify,.verify-button,[class*=verify]'); + if(btn){var r=btn.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + btn.dispatchEvent(new PointerEvent('pointerdown',ev)); + btn.dispatchEvent(new MouseEvent('mousedown',ev)); + btn.dispatchEvent(new PointerEvent('pointerup',ev)); + btn.dispatchEvent(new MouseEvent('mouseup',ev)); + btn.dispatchEvent(new MouseEvent('click',ev)); + return 'clicked';}return 'not_found';})() + "#; + if let Err(e) = eval_with_timeout( + page, + verify_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("TTT verify JS failed: {}", e); + } + log::info!("TTT: engine clicked verify after win"); + tokio::time::sleep( + std::time::Duration::from_millis(1500), + ) + .await; + // Update title for model context + let post = + self.title_context(page, &effective_cfg).await; + if !post.is_empty() { + title_now = post; + } + } + break; + } + if full { + // Draw — click refresh to reset, then play again + games_played += 1; + if games_played >= 3 { + break; + } + log::info!( + "TTT draw (game {}), clicking refresh...", + games_played + ); + let refresh_js = r#"(function(){var btn=document.querySelector('.captcha-refresh,[class*=refresh],[class*=retry]');if(btn){btn.click();return 'clicked';}return 'not_found';})()"#; + if let Err(e) = eval_with_timeout( + page, + refresh_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("TTT refresh JS failed: {}", e); + } + tokio::time::sleep(std::time::Duration::from_millis( + 2000, + )) + .await; + } else { + // Wait for AI response + tokio::time::sleep(std::time::Duration::from_millis( + 700, + )) + .await; + } + // Re-run pre_evaluate for next move + if let Err(e) = + eval_with_timeout(page, ttt_js, EVAL_TIMEOUT_SECS).await + { + log::warn!("TTT pre_evaluate re-run failed: {}", e); + } + let updated = + self.title_context(page, &effective_cfg).await; + if !updated.is_empty() { + log::info!( + "TTT loop (game {}): '{}'", + games_played + 1, + &updated[..updated.len().min(80)] + ); + title_now = updated; + } + } + } + } + + // WAM: Whack-a-Mole engine loop — repeatedly detect and click moles + // since they appear/disappear too fast for LLM round-trips. + if new_title.starts_with("WAM:") { + let wam_js: Option<&str> = pre_evals + .iter() + .find(|(name, _)| *name == "whack-a-mole") + .map(|(_, js)| *js); + if let Some(wam_js) = wam_js { + let mut total_hits = 0u32; + // Run up to 20 iterations over ~10 seconds + for _wam_step in 0..20 { + if let Some(json_str) = title_now.strip_prefix("WAM:") { + if let Ok(data) = + serde_json::from_str::(json_str) + { + let clicked = data + .get("clicked") + .and_then(|v| v.as_u64()) + .unwrap_or(0) + as u32; + total_hits += clicked; + } + } + if total_hits >= 5 { + break; + } + // Wait for new moles to appear + tokio::time::sleep(std::time::Duration::from_millis(500)) + .await; + // Re-run pre_evaluate to detect and click new moles + if let Err(e) = + eval_with_timeout(page, wam_js, EVAL_TIMEOUT_SECS).await + { + log::warn!("WAM pre_evaluate re-run failed: {}", e); + } + let updated = + self.title_context(page, &effective_cfg).await; + if !updated.is_empty() { + title_now = updated; + } + } + if total_hits >= 5 { + log::info!( + "WAM: engine hit {} moles, clicking verify", + total_hits + ); + // Mark done to prevent re-running + let done_title = + format!("WAM_DONE:{{\"hits\":{}}}", total_hits); + let marker_js = format!( + "document.title={t};if(!document.getElementById('wam-engine-done')){{var d=document.createElement('div');d.id='wam-engine-done';d.style.display='none';d.dataset.t={t};document.body.appendChild(d);}}", + t = serde_json::to_string(&done_title).unwrap_or_default() + ); + if let Err(e) = + eval_with_timeout(page, marker_js, EVAL_TIMEOUT_SECS) + .await + { + log::warn!("WAM marker JS failed: {}", e); + } + tokio::time::sleep(std::time::Duration::from_millis(500)) + .await; + let verify_js = r#"(function(){ + var btn=document.querySelector('#captcha-verify-button,button.captcha-verify,.verify-button,[class*=verify]'); + if(btn){var r=btn.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + btn.dispatchEvent(new PointerEvent('pointerdown',ev)); + btn.dispatchEvent(new MouseEvent('mousedown',ev)); + btn.dispatchEvent(new PointerEvent('pointerup',ev)); + btn.dispatchEvent(new MouseEvent('mouseup',ev)); + btn.dispatchEvent(new MouseEvent('click',ev)); + return 'clicked';}return 'not_found';})() + "#; + if let Err(e) = + eval_with_timeout(page, verify_js, EVAL_TIMEOUT_SECS) + .await + { + log::warn!("WAM verify JS failed: {}", e); + } + tokio::time::sleep(std::time::Duration::from_millis(1500)) + .await; + let post = self.title_context(page, &effective_cfg).await; + if !post.is_empty() { + title_now = post; + } + } + } + } + + // NEST: Nested grid engine loop — detect stop sign overlap + // and click boxes automatically, wait for subdivision, repeat. + if new_title.starts_with("NEST:") { + let nest_js: Option<&str> = pre_evals + .iter() + .find(|(name, _)| *name == "nested-grid") + .map(|(_, js)| *js); + if let Some(nest_js) = nest_js { + let mut total_clicked = 0u32; + // Up to 8 subdivision rounds + for _nest_step in 0..8 { + if let Some(json_str) = title_now.strip_prefix("NEST:") { + if let Ok(data) = + serde_json::from_str::(json_str) + { + let has_sign = data + .get("hasSign") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let to_click = + data.get("toClick").and_then(|v| v.as_array()); + let boxes = + data.get("boxes").and_then(|v| v.as_array()); + if !has_sign { + break; + } // can't detect sign, let LLM handle + if let (Some(to_click), Some(boxes)) = + (to_click, boxes) + { + if to_click.is_empty() { + break; + } // all done + // Build id→center map + let box_map: std::collections::HashMap< + i64, + (f64, f64), + > = boxes + .iter() + .filter_map(|b| { + let id = b + .get("id") + .and_then(|v| v.as_i64())?; + let x = b + .get("x") + .and_then(|v| v.as_f64())?; + let y = b + .get("y") + .and_then(|v| v.as_f64())?; + Some((id, (x, y))) + }) + .collect(); + use chromiumoxide::cdp::browser_protocol::input::{ + DispatchMouseEventParams, DispatchMouseEventType, MouseButton, + }; + let mut clicked_this_round = 0u32; + let mut cdp_errors = 0u32; + for tc_id in to_click { + let id = tc_id.as_i64().unwrap_or(-1); + if let Some((x, y)) = box_map.get(&id) { + // CDP click: move → press → release + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(*x).y(*y) + .button(MouseButton::None).buttons(0) + .r#type(DispatchMouseEventType::MouseMoved).build() { + if page.send_command(cmd).await.is_err() { cdp_errors += 1; } + } + tokio::time::sleep( + std::time::Duration::from_millis( + 30, + ), + ) + .await; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(*x).y(*y) + .button(MouseButton::Left).buttons(1).click_count(1) + .r#type(DispatchMouseEventType::MousePressed).build() { + if page.send_command(cmd).await.is_err() { cdp_errors += 1; } + } + tokio::time::sleep( + std::time::Duration::from_millis( + 30, + ), + ) + .await; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(*x).y(*y) + .button(MouseButton::Left).buttons(0).click_count(1) + .r#type(DispatchMouseEventType::MouseReleased).build() { + if page.send_command(cmd).await.is_err() { cdp_errors += 1; } + } + tokio::time::sleep( + std::time::Duration::from_millis( + 80, + ), + ) + .await; + clicked_this_round += 1; + } + } + if cdp_errors > 0 { + log::warn!("NEST: {cdp_errors} CDP command(s) failed this iteration"); + } + total_clicked += clicked_this_round; + log::info!( + "NEST: clicked {} boxes (total {})", + clicked_this_round, + total_clicked + ); + // Wait for subdivision animation + tokio::time::sleep( + std::time::Duration::from_millis(1200), + ) + .await; + // Re-run pre_evaluate to detect new leaf boxes + if let Err(e) = eval_with_timeout( + page, + nest_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!( + "NEST pre_evaluate re-run failed: {}", + e + ); + } + let updated = self + .title_context(page, &effective_cfg) + .await; + if !updated.is_empty() { + title_now = updated; + } + } else { + break; + } + } else { + break; + } + } else { + break; + } + } + if total_clicked > 0 { + log::info!("NEST: engine auto-clicked {} total boxes, clicking verify", total_clicked); + let done_title = + format!("NEST_DONE:{{\"clicked\":{}}}", total_clicked); + let marker_js = format!( + "document.title={t};if(!document.getElementById('nest-engine-done')){{var d=document.createElement('div');d.id='nest-engine-done';d.style.display='none';d.dataset.t={t};document.body.appendChild(d);}}", + t = serde_json::to_string(&done_title).unwrap_or_default() + ); + if let Err(e) = + eval_with_timeout(page, marker_js, EVAL_TIMEOUT_SECS) + .await + { + log::warn!("NEST marker JS failed: {}", e); + } + tokio::time::sleep(std::time::Duration::from_millis(500)) + .await; + let verify_js = r#"(function(){ + var btn=document.querySelector('#captcha-verify-button,button.captcha-verify,.verify-button,[class*=verify]'); + if(btn){var r=btn.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + btn.dispatchEvent(new PointerEvent('pointerdown',ev)); + btn.dispatchEvent(new MouseEvent('mousedown',ev)); + btn.dispatchEvent(new PointerEvent('pointerup',ev)); + btn.dispatchEvent(new MouseEvent('mouseup',ev)); + btn.dispatchEvent(new MouseEvent('click',ev)); + return 'clicked';}return 'not_found';})() + "#; + if let Err(e) = + eval_with_timeout(page, verify_js, EVAL_TIMEOUT_SECS) + .await + { + log::warn!("NEST verify JS failed: {}", e); + } + log::info!("NEST: engine clicked verify"); + tokio::time::sleep(std::time::Duration::from_millis(1500)) + .await; + let post = self.title_context(page, &effective_cfg).await; + if !post.is_empty() { + title_now = post; + } + } + } + } + + // CIRCLE: engine draws a circle via CDP mouse drag through + // pre-computed points, then clicks verify. + if new_title.starts_with("CIRCLE:") { + if let Some(json_str) = new_title.strip_prefix("CIRCLE:") { + if let Ok(data) = + serde_json::from_str::(json_str) + { + let pts = data.get("pts").and_then(|v| v.as_array()); + if let Some(pts) = pts { + let coords: Vec<(f64, f64)> = pts + .iter() + .filter_map(|p| { + let x = p.get("x").and_then(|v| v.as_f64())?; + let y = p.get("y").and_then(|v| v.as_f64())?; + Some((x, y)) + }) + .collect(); + if coords.len() >= 2 { + use chromiumoxide::cdp::browser_protocol::input::{ + DispatchMouseEventParams, + DispatchMouseEventType, MouseButton, + }; + let mut cdp_errors = 0u32; + // Move to start + let (sx, sy) = coords[0]; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(sx) + .y(sy) + .button(MouseButton::None) + .buttons(0) + .r#type(DispatchMouseEventType::MouseMoved) + .build() + { + if page.send_command(cmd).await.is_err() { + cdp_errors += 1; + } + } + tokio::time::sleep( + std::time::Duration::from_millis(50), + ) + .await; + // Press at start + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(sx) + .y(sy) + .button(MouseButton::Left) + .buttons(1) + .r#type(DispatchMouseEventType::MousePressed) + .build() + { + if page.send_command(cmd).await.is_err() { + cdp_errors += 1; + } + } + // Drag through all points + for (x, y) in &coords[1..] { + tokio::time::sleep( + std::time::Duration::from_millis(20), + ) + .await; + if let Ok(cmd) = + DispatchMouseEventParams::builder() + .x(*x) + .y(*y) + .button(MouseButton::Left) + .buttons(1) + .r#type( + DispatchMouseEventType::MouseMoved, + ) + .build() + { + if page.send_command(cmd).await.is_err() { + cdp_errors += 1; + } + } + } + // Release at end + let (ex, ey) = *coords.last().unwrap_or(&(sx, sy)); + tokio::time::sleep( + std::time::Duration::from_millis(30), + ) + .await; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(ex) + .y(ey) + .button(MouseButton::Left) + .buttons(0) + .r#type(DispatchMouseEventType::MouseReleased) + .build() + { + if page.send_command(cmd).await.is_err() { + cdp_errors += 1; + } + } + if cdp_errors > 0 { + log::warn!("CIRCLE: {cdp_errors} CDP command(s) failed during draw"); + } + log::info!( + "CIRCLE: engine drew circle with {} points", + coords.len() + ); + let done_title = format!( + "CIRCLE_DONE:{{\"points\":{}}}", + coords.len() + ); + let marker_js = format!( + "document.title={t};if(!document.getElementById('circle-engine-done')){{var d=document.createElement('div');d.id='circle-engine-done';d.style.display='none';d.dataset.t={t};document.body.appendChild(d);}}", + t = serde_json::to_string(&done_title).unwrap_or_default() + ); + if let Err(e) = eval_with_timeout( + page, + marker_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("CIRCLE marker JS failed: {}", e); + } + tokio::time::sleep( + std::time::Duration::from_millis(1000), + ) + .await; + let verify_js = r#"(function(){ + var btn=document.querySelector('#captcha-verify-button,button.captcha-verify,.verify-button,[class*=verify]'); + if(btn){var r=btn.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + btn.dispatchEvent(new PointerEvent('pointerdown',ev)); + btn.dispatchEvent(new MouseEvent('mousedown',ev)); + btn.dispatchEvent(new PointerEvent('pointerup',ev)); + btn.dispatchEvent(new MouseEvent('mouseup',ev)); + btn.dispatchEvent(new MouseEvent('click',ev)); + return 'clicked';}return 'not_found';})() + "#; + if let Err(e) = eval_with_timeout( + page, + verify_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("CIRCLE verify JS failed: {}", e); + } + log::info!("CIRCLE: engine clicked verify"); + tokio::time::sleep( + std::time::Duration::from_millis(1500), + ) + .await; + let post = + self.title_context(page, &effective_cfg).await; + if !post.is_empty() { + title_now = post; + } + } + } + } + } + } + + // WS_DRAG: engine clicks each word-search cell individually via CDP. + // Each cell gets a full click (move→press→release) for reliable + // click-to-select behavior, then engine clicks verify. + if new_title.starts_with("WS_DRAG:") { + if let Some(json_str) = new_title.strip_prefix("WS_DRAG:") { + if let Ok(data) = + serde_json::from_str::(json_str) + { + let words = data.get("words").and_then(|v| v.as_array()); + let drags = data.get("drags").and_then(|v| v.as_array()); + if let (Some(words), Some(drags)) = (words, drags) { + let mut dragged = Vec::new(); + let mut all_coords: Vec<(f64, f64)> = Vec::new(); + for (wi, drag_pts) in drags.iter().enumerate() { + if let Some(pts) = drag_pts.as_array() { + let coords: Vec<(f64, f64)> = pts + .iter() + .filter_map(|p| { + let x = p + .get("x") + .and_then(|v| v.as_f64())?; + let y = p + .get("y") + .and_then(|v| v.as_f64())?; + Some((x, y)) + }) + .collect(); + if !coords.is_empty() { + all_coords.extend_from_slice(&coords); + if let Some(w) = + words.get(wi).and_then(|v| v.as_str()) + { + dragged.push(w.to_string()); + } + } + } + } + // Deduplicate cells by rounded coordinate + { + let mut seen = std::collections::HashSet::new(); + all_coords.retain(|(x, y)| { + seen.insert((*x as i64, *y as i64)) + }); + } + if !all_coords.is_empty() { + use chromiumoxide::cdp::browser_protocol::input::{ + DispatchMouseEventParams, + DispatchMouseEventType, MouseButton, + }; + let mut cdp_errors = 0u32; + // Click each cell center individually (click-to-select) + for (x, y) in &all_coords { + if let Ok(cmd) = + DispatchMouseEventParams::builder() + .x(*x) + .y(*y) + .button(MouseButton::None) + .buttons(0) + .r#type( + DispatchMouseEventType::MouseMoved, + ) + .build() + { + if page.send_command(cmd).await.is_err() { + cdp_errors += 1; + } + } + tokio::time::sleep( + std::time::Duration::from_millis(20), + ) + .await; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(*x).y(*y) + .button(MouseButton::Left).buttons(1).click_count(1) + .r#type(DispatchMouseEventType::MousePressed).build() { + if page.send_command(cmd).await.is_err() { cdp_errors += 1; } + } + tokio::time::sleep( + std::time::Duration::from_millis(20), + ) + .await; + if let Ok(cmd) = DispatchMouseEventParams::builder() + .x(*x).y(*y) + .button(MouseButton::Left).buttons(0).click_count(1) + .r#type(DispatchMouseEventType::MouseReleased).build() { + if page.send_command(cmd).await.is_err() { cdp_errors += 1; } + } + tokio::time::sleep( + std::time::Duration::from_millis(50), + ) + .await; + } + if cdp_errors > 0 { + log::warn!("WS: {cdp_errors} CDP command(s) failed during cell clicks"); + } + } + if !dragged.is_empty() { + log::info!( + "WS engine clicked {} cells for {} words: {:?}", + all_coords.len(), + dragged.len(), + dragged + ); + let done_title = format!( + "WS_DONE:{{\"dragged\":{}}}", + serde_json::to_string(&dragged) + .unwrap_or_default() + ); + // Set title + DOM marker to prevent re-clicking + let marker_js = format!( + "document.title={t};if(!document.getElementById('ws-engine-done')){{var d=document.createElement('div');d.id='ws-engine-done';d.style.display='none';d.dataset.t={t};document.body.appendChild(d);}}", + t = serde_json::to_string(&done_title).unwrap_or_default() + ); + if let Err(e) = eval_with_timeout( + page, + marker_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("WS marker JS failed: {}", e); + } + // Engine clicks verify immediately after selecting cells + tokio::time::sleep( + std::time::Duration::from_millis(500), + ) + .await; + let verify_js = r#"(function(){ + var btn=document.querySelector('#captcha-verify-button,button.captcha-verify,.verify-button,[class*=verify]'); + if(btn){var r=btn.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + btn.dispatchEvent(new PointerEvent('pointerdown',ev)); + btn.dispatchEvent(new MouseEvent('mousedown',ev)); + btn.dispatchEvent(new PointerEvent('pointerup',ev)); + btn.dispatchEvent(new MouseEvent('mouseup',ev)); + btn.dispatchEvent(new MouseEvent('click',ev)); + return 'clicked';}return 'not_found';})() + "#; + if let Err(e) = eval_with_timeout( + page, + verify_js, + EVAL_TIMEOUT_SECS, + ) + .await + { + log::warn!("WS verify JS failed: {}", e); + } + log::info!("WS: engine clicked verify after selecting cells"); + tokio::time::sleep( + std::time::Duration::from_millis(1500), + ) + .await; + let post = + self.title_context(page, &effective_cfg).await; + title_now = if !post.is_empty() { + post + } else { + done_title + }; + } + } + } + } + } + } + } + } + } + + // Re-evaluate vision decision now that stagnation is known. + // If stagnation/stuck upgraded us from text→vision, capture screenshot now. + let use_vision = if !use_vision + && self.has_dual_model_routing() + && self.should_use_vision_this_round( + round_idx, + stagnated, + action_stuck_rounds, + false, + ) { + true // upgraded to vision mid-round + } else { + use_vision + }; + + // Late screenshot capture when upgrading to vision after stagnation detected + let include_screenshot_this_round = use_vision + && !skip_screenshot_for_extraction + && self.should_include_screenshot_for_round(&effective_cfg, use_vision); + let screenshot = if include_screenshot_this_round && screenshot.is_empty() { + match self.screenshot_as_data_url_with_profile(page, cap).await { + Ok(s) => s, + Err(e) => { + log::warn!("Late screenshot failed (non-fatal): {e}"); + String::new() + } + } + } else { + screenshot + }; + + // Prepend action feedback from previous round to user message extra + // (only when at least one action failed, to avoid noise). + let user_extra_with_feedback: Option = + match (&last_action_feedback, &effective_user_message_extra) { + (Some(feedback), Some(extra)) => { + let mut combined = feedback.clone(); + combined.push_str(extra); + Some(combined) + } + (Some(feedback), None) => Some(feedback.clone()), + (None, Some(extra)) => Some(extra.clone()), + (None, None) => None, + }; + // Clear feedback after injecting it (one-shot per round) + last_action_feedback = None; + + // Ask model (with retry policy) - pass memory as immutable ref for context + let plan = if use_chrome_ai { + self.infer_plan_chrome_ai_with_retry( + page, + &effective_cfg, + cap, + url_input, + &url_now, + &title_now, + &html, + &screenshot, + round_idx, + stagnated, + action_stuck_rounds, + &loop_blocklist, + memory.as_deref(), + effective_system_prompt.as_deref(), + effective_system_prompt_extra.as_deref(), + user_extra_with_feedback.as_deref(), + ) + .await + } else { + self.infer_plan_with_retry( + &effective_cfg, + cap, + url_input, + &url_now, + &title_now, + &html, + &screenshot, + round_idx, + stagnated, + action_stuck_rounds, + &loop_blocklist, + memory.as_deref(), + use_vision, + effective_system_prompt.as_deref(), + effective_system_prompt_extra.as_deref(), + user_extra_with_feedback.as_deref(), + ) + .await + }; + + // Improvement #2: Preserve partial results on fatal LLM errors. + // Instead of propagating the error with `?`, catch it and return + // accumulated data from prior rounds. + let plan = match plan { + Ok(p) => p, + Err(e) => { + log::error!( + "LLM inference failed after retries on round {}: {}", + round_idx + 1, + e + ); + let final_screenshot = if effective_cfg.screenshot { + self.take_final_screenshot(page).await.ok() + } else { + None + }; + return Ok(AutomationResult { + label: last_label, + steps_executed: total_steps_executed, + success: false, + error: Some(format!( + "LLM inference failed on round {}: {}", + round_idx + 1, + e + )), + usage: total_usage, + extracted: last_extracted, + screenshot: final_screenshot, + spawn_pages: all_spawn_pages, + relevant: last_relevant, + reasoning: last_reasoning, + }); + } + }; + + // Accumulate token usage from this round + total_usage.accumulate(&plan.usage); + last_label = plan.label.clone(); + + // Stuck-loop detection: hash step structure (ignoring Evaluate code content + // which LLMs often vary slightly while functionally repeating the same approach) + { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for step in &plan.steps { + if let Some(obj) = step.as_object() { + for (key, val) in obj { + key.hash(&mut hasher); + // Hash Evaluate as just the action name, not the JS code + if key != "Evaluate" { + val.to_string().hash(&mut hasher); + } + } + } + } + let step_hash = hasher.finish(); + + recent_step_hashes.push_back(step_hash); + if recent_step_hashes.len() > 10 { + recent_step_hashes.pop_front(); + } + + action_stuck_rounds = recent_step_hashes + .iter() + .rev() + .take_while(|h| **h == step_hash) + .count(); + + if action_stuck_rounds >= 3 { + loop_blocklist = Self::summarize_step_blocklist(&plan.steps, 10); + // Escalate only when a vision-capable route exists. + if self.has_vision_capable_route() { + force_vision_next_round = true; + } + log::warn!( + "Action loop detected: {} consecutive identical step sequences", + action_stuck_rounds + ); + } else if !stagnated { + loop_blocklist.clear(); + } + } + + // Process memory operations from the plan + if let Some(ref mut mem) = memory { + for op in &plan.memory_ops { + match op { + MemoryOperation::Set { key, value } => { + // Detect `request_vision` memory_op for dual-model routing + if key == "request_vision" { + if self.has_vision_capable_route() { + force_vision_next_round = true; + log::debug!("Agent requested vision for next round"); + } + continue; // don't persist this transient key + } + mem.set(key.clone(), value.clone()); + } + MemoryOperation::Delete { key } => { + mem.remove(key); + } + MemoryOperation::Clear => { + mem.clear_store(); + } + } + } + // Handle skill requests via memory_ops + #[cfg(feature = "skills")] + if let Some(ref registry) = self.skill_registry { + for op in &plan.memory_ops { + if let MemoryOperation::Set { key, value } = op { + if key == "request_skill" { + if let Some(skill_name) = value.as_str() { + if registry.get(skill_name).is_some() { + log::info!("Agent requested skill: {}", skill_name); + mem.set( + "_active_skill".to_string(), + serde_json::json!(skill_name), + ); + } + } + } + } + } + } + // Record this round's URL and action + mem.add_visited_url(&url_now); + mem.add_action(format!("Round {}: {}", round_idx + 1, &plan.label)); + } + + // Save relevance flag if present + if plan.relevant.is_some() { + last_relevant = plan.relevant; + } + if plan.reasoning.is_some() { + last_reasoning = plan.reasoning.clone(); + } + + // Save extracted data if present + if plan.extracted.is_some() { + last_extracted = plan.extracted.clone(); + // Also store in memory if available + if let (Some(ref mut mem), Some(ref extracted)) = (&mut memory, &plan.extracted) { + mem.add_extraction(extracted.clone()); + if let Some(level_key) = Self::extracted_level_key(Some(extracted)) { + let attempts = mem.increment_level_attempt(&level_key); + mem.set("_current_level_key", serde_json::json!(level_key)); + mem.set("_current_level_attempts", serde_json::json!(attempts)); + current_level_attempts = Some(attempts); + } + } + } + + // Execute steps (even if done=true, we need to process OpenPage actions). + // When stuck in a loop for multiple rounds, skip repeated steps and auto-inspect DOM. + let has_structured_level_state = current_level_attempts.is_some(); + let should_force_level_refresh = !plan.done + && has_structured_level_state + && current_level_attempts.unwrap_or(0) >= 12; + + if should_force_level_refresh { + log::warn!( + "Level attempts reached {} - forcing captcha refresh recovery", + current_level_attempts.unwrap_or(0) + ); + // Use dispatchEvent with full pointer/mouse events (not el.click() + // which doesn't trigger real browser events on most pages). + // Generic selectors cover refresh/retry/reset patterns dynamically. + let recovery_steps = vec![ + serde_json::json!({ "Evaluate": r#" +(() => { + const sels = [ + '[class*=refresh]', '[data-action=refresh]', + '[aria-label*=refresh i]', '[title*=refresh i]', + 'button[id*=refresh i]', 'button[class*=refresh i]', + 'button[id*=retry i]', 'button[class*=retry i]', + 'button[id*=reset i]', 'button[class*=reset i]', + 'button[id*=new i]', 'a[id*=refresh i]' + ]; + for (const sel of sels) { + const el = document.querySelector(sel); + if (el && !el.disabled && el.offsetParent !== null) { + const r = el.getBoundingClientRect(); + const cx = r.x + r.width / 2, cy = r.y + r.height / 2; + const opts = { bubbles: true, cancelable: true, clientX: cx, clientY: cy }; + el.dispatchEvent(new PointerEvent('pointerdown', opts)); + el.dispatchEvent(new MouseEvent('mousedown', opts)); + el.dispatchEvent(new PointerEvent('pointerup', opts)); + el.dispatchEvent(new MouseEvent('mouseup', opts)); + el.dispatchEvent(new MouseEvent('click', opts)); + document.title = 'RECOVERY:clicked=' + sel; + return; + } + } + document.title = 'RECOVERY:no_refresh_btn'; +})() +"# }), + serde_json::json!({ "Wait": 1000 }), + // Clear stale RECOVERY title so it doesn't confuse next rounds + serde_json::json!({ "Evaluate": "try{document.title=document.title.replace(/^RECOVERY:.*/,'')}catch(e){}" }), + ]; + let (steps_executed, spawn_pages, _recovery_outcomes) = self + .execute_steps(page, &recovery_steps, &effective_cfg) + .await?; + total_steps_executed += steps_executed; + all_spawn_pages.extend(spawn_pages); + recent_step_hashes.clear(); + action_stuck_rounds = 0; + loop_blocklist.clear(); + if self.has_vision_capable_route() { + force_vision_next_round = true; + } + if let Some(ref mut mem) = memory { + // Reset level attempt counter so the agent gets fresh tries + if let Some(ref key) = mem + .get("_current_level_key") + .and_then(|v| v.as_str().map(String::from)) + { + mem.reset_level_attempt(key); + } + mem.add_action(format!( + "SYSTEM: Forced refresh recovery after {} attempts on same level. Counter reset.", + current_level_attempts.unwrap_or(0) + )); + } + } else if action_stuck_rounds >= 5 { + let stuck_rounds = action_stuck_rounds; + log::warn!( + "Skipping {} repeated steps - auto-inspecting DOM (stuck {} rounds)", + plan.steps.len(), + stuck_rounds + ); + if self.has_vision_capable_route() { + force_vision_next_round = true; + } + // Inject DOM inspection so the model gets real state data next round + if let Err(e) = page + .evaluate( + r#"document.title = 'AUTO_DOM_INSPECT:' + JSON.stringify({ + level_text: (document.querySelector('h2,h3,.level-title,.challenge-title')?.textContent || '').trim().slice(0, 120), + prompt_text: (document.querySelector('.prompt,.instruction,.challenge-prompt,.captcha-instructions')?.textContent || document.body?.innerText || '').trim().slice(0, 180), + selected_count: [...document.querySelectorAll( + '.selected,.active,[aria-checked="true"],[aria-pressed="true"],[class*="selected"],[class*="active"]' + )].length, + verify_buttons: [...document.querySelectorAll('button,input[type=button],input[type=submit]')].slice(0, 6).map(el => ({ + text: (el.textContent || el.value || '').trim().slice(0, 40), + dis: !!el.disabled + })), + els: [...document.querySelectorAll( + 'button, [onclick], [class*=item], [class*=cell], [class*=grid] > *, input, select, [class*=square], [class*=piece], [class*=tile]' + )].slice(0, 30).map((el, i) => ({ + n: i, + tag: el.tagName, + cls: el.className.split(' ').slice(0, 4).join(' '), + text: el.textContent.trim().slice(0, 50), + sel: el.classList.contains('selected') || el.classList.contains('active') || el.getAttribute('aria-checked') === 'true', + dis: el.disabled || el.classList.contains('disabled') + })) + })"#, + ) + .await + { + log::warn!("AUTO_DOM_INSPECT failed: {e}"); + } + // Reset loop streak after injecting explicit recovery context. + // This avoids burning rounds on repeated skip-only cycles. + recent_step_hashes.clear(); + action_stuck_rounds = 0; + // Record in memory that DOM was auto-inspected + if let Some(ref mut mem) = memory { + mem.add_action(format!( + "SYSTEM: Skipped repeated steps (stuck {}x). DOM auto-inspected - check page title for element states.", + stuck_rounds + )); + } + } else if !plan.steps.is_empty() { + let (steps_executed, spawn_pages, outcomes) = self + .execute_steps(page, &plan.steps, &effective_cfg) + .await?; + total_steps_executed += steps_executed; + all_spawn_pages.extend(spawn_pages); + last_action_feedback = format_action_feedback(&outcomes); + } + + // Done condition (model-driven) + if plan.done { + // Store successful experience in long-term memory + #[cfg(feature = "memvid")] + if total_steps_executed > 0 { + if let Some(ref exp_mem) = self.experience_memory { + if let Some(ref mem) = memory { + let record = super::long_term_memory::ExperienceRecord::from_session( + url_input, + &plan.label, + mem, + total_steps_executed, + (round_idx + 1) as u32, + ); + let exp_mem = exp_mem.clone(); + let handle = tokio::runtime::Handle::current(); + let _ = tokio::task::spawn_blocking(move || { + handle.block_on(async move { + let mut exp = exp_mem.write().await; + if let Err(e) = exp.store_experience(&record).await { + log::warn!("Failed to store experience: {}", e); + } + }) + }) + .await; + } + } + } + + // Capture final screenshot (enabled by default) + let final_screenshot = if effective_cfg.screenshot { + self.take_final_screenshot(page).await.ok() + } else { + None + }; + + return Ok(AutomationResult { + label: plan.label, + steps_executed: total_steps_executed, + success: true, + error: None, + usage: total_usage, + extracted: last_extracted, + screenshot: final_screenshot, + spawn_pages: all_spawn_pages, + relevant: last_relevant, + reasoning: last_reasoning, + }); + } + + // Post-step delay (capped at 30s to prevent misconfigured values from blocking) + if effective_cfg.post_plan_wait_ms > 0 { + tokio::time::sleep(std::time::Duration::from_millis( + effective_cfg.post_plan_wait_ms.min(30_000), + )) + .await; + } + } + + // Store experience after all rounds complete + #[cfg(feature = "memvid")] + if total_steps_executed > 0 { + if let Some(ref exp_mem) = self.experience_memory { + if let Some(ref mem) = memory { + let record = super::long_term_memory::ExperienceRecord::from_session( + url_input, + &last_label, + mem, + total_steps_executed, + rounds as u32, + ); + let exp_mem = exp_mem.clone(); + let handle = tokio::runtime::Handle::current(); + let _ = tokio::task::spawn_blocking(move || { + handle.block_on(async move { + let mut exp = exp_mem.write().await; + if let Err(e) = exp.store_experience(&record).await { + log::warn!("Failed to store experience: {}", e); + } + }) + }) + .await; + } + } + } + + // Final screenshot after all rounds + let final_screenshot = if effective_cfg.screenshot { + self.take_final_screenshot(page).await.ok() + } else { + None + }; + + Ok(AutomationResult { + label: last_label, + steps_executed: total_steps_executed, + success: false, + error: Some(format!( + "automation did not complete within {} round(s)", + rounds + )), + usage: total_usage, + extracted: last_extracted, + screenshot: final_screenshot, + spawn_pages: all_spawn_pages, + relevant: last_relevant, + reasoning: last_reasoning, + }) + } + + /// Infer plan with retry policy. + #[allow(clippy::too_many_arguments)] + async fn infer_plan_with_retry( + &self, + effective_cfg: &RemoteMultimodalConfig, + cap: &CaptureProfile, + url_input: &str, + url_now: &str, + title_now: &str, + html: &str, + screenshot: &str, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + loop_blocklist: &[String], + memory: Option<&AutomationMemory>, + use_vision: bool, + base_system_prompt: Option<&str>, + system_prompt_extra: Option<&str>, + user_message_extra: Option<&str>, + ) -> EngineResult { + let max_attempts = effective_cfg.retry.max_attempts.max(1); + let mut last_err: Option = None; + // Track which models have been tried so we can rotate on retryable errors + let mut tried_models: Vec = Vec::new(); + + for attempt in 0..max_attempts { + // On retryable errors (502, 503, 429, timeout), try a different model from the pool + let model_override = if attempt > 0 && !self.model_pool.is_empty() { + if let Some(ref err) = last_err { + if err.is_retryable_on_different_model() { + self.pick_fallback_model(&tried_models, use_vision) + } else { + None + } + } else { + None + } + } else { + None + }; + + if let Some((ref u, ref m, _)) = model_override { + log::warn!( + "Attempt {}/{}: falling back to model {} at {} after retryable error", + attempt + 1, + max_attempts, + m, + u, + ); + } + + let override_refs = model_override + .as_ref() + .map(|(u, m, k)| (u.as_str(), m.as_str(), k.as_deref())); + + match self + .infer_plan_once( + effective_cfg, + cap, + url_input, + url_now, + title_now, + html, + screenshot, + round_idx, + stagnated, + action_stuck_rounds, + loop_blocklist, + memory, + use_vision, + base_system_prompt, + system_prompt_extra, + user_message_extra, + override_refs, + ) + .await + { + Ok(plan) => return Ok(plan), + Err(e) => { + // Record which model was attempted + if let Some((_, ref m, _)) = model_override { + if !tried_models.contains(m) { + tried_models.push(m.clone()); + } + } else if tried_models.is_empty() { + // First attempt used the default model + tried_models.push(self.model_name.clone()); + } + + log::warn!( + "Attempt {}/{} failed: {}{}", + attempt + 1, + max_attempts, + e, + if e.is_retryable_on_different_model() && !self.model_pool.is_empty() { + " (will try different model)" + } else { + "" + } + ); + + last_err = Some(e); + if attempt + 1 < max_attempts { + // Exponential backoff with capped power and max delay + let power = attempt.min(6); + let delay = (effective_cfg.retry.backoff_ms * (1 << power)).min(60_000); + tokio::time::sleep(std::time::Duration::from_millis(delay)).await; + } + } + } + } + + Err(last_err.unwrap_or_else(|| EngineError::Remote("max retries exceeded".to_string()))) + } + + /// Single plan inference attempt. + #[allow(clippy::too_many_arguments)] + async fn infer_plan_once( + &self, + effective_cfg: &RemoteMultimodalConfig, + cap: &CaptureProfile, + url_input: &str, + url_now: &str, + title_now: &str, + html: &str, + screenshot: &str, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + loop_blocklist: &[String], + memory: Option<&AutomationMemory>, + use_vision: bool, + base_system_prompt: Option<&str>, + system_prompt_extra: Option<&str>, + user_message_extra: Option<&str>, + model_override: Option<(&str, &str, Option<&str>)>, + ) -> EngineResult { + use super::{ + best_effort_parse_json_object, effective_thinking_payload, extract_assistant_content, + extract_thinking_content, extract_usage, is_anthropic_endpoint, reasoning_payload, + DEFAULT_SYSTEM_PROMPT, EXTRACTION_ONLY_SYSTEM_PROMPT, + }; + use serde::Serialize; + + #[derive(Serialize)] + struct Message { + role: String, + content: serde_json::Value, + } + + #[derive(Serialize)] + struct Request { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_tokens: Option, + #[serde(skip_serializing_if = "Option::is_none")] + response_format: Option, + #[serde(skip_serializing_if = "Option::is_none")] + reasoning: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thinking: Option, + #[serde(skip_serializing_if = "Option::is_none")] + system: Option, + #[serde(skip_serializing_if = "Option::is_none")] + tools: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + tool_choice: Option, + } + + // Build system prompt — use focused extraction prompt for single-round extraction + let mut system_msg = if effective_cfg.is_extraction_only() { + EXTRACTION_ONLY_SYSTEM_PROMPT.to_string() + } else { + DEFAULT_SYSTEM_PROMPT.to_string() + }; + if let Some(base) = base_system_prompt { + if !base.trim().is_empty() { + system_msg.push_str("\n\n---\nCONFIGURED SYSTEM INSTRUCTIONS:\n"); + system_msg.push_str(base.trim()); + } + } + if let Some(extra) = system_prompt_extra { + if !extra.trim().is_empty() { + system_msg.push_str("\n\n---\nADDITIONAL INSTRUCTIONS:\n"); + system_msg.push_str(extra.trim()); + } + } + // Inject matching skills from the skill registry (limited by config) + #[cfg(feature = "skills")] + if let Some(ref registry) = self.skill_registry { + log::debug!( + "Skill registry: {} skills, checking url={} title={} html_len={}", + registry.len(), + url_now, + title_now, + html.len() + ); + let mut skill_ctx = registry.match_context_limited( + url_now, + title_now, + html, + effective_cfg.max_skills_per_round, + effective_cfg.max_skill_context_chars, + ); + // Also inject agent-requested skills from memory + if let Some(mem) = memory { + if let Some(active) = mem.get("_active_skill") { + if let Some(name) = active.as_str() { + if let Some(skill) = registry.get(name) { + if !skill_ctx.contains(&skill.name) { + if !skill_ctx.is_empty() { + skill_ctx.push_str("\n\n"); + } + skill_ctx.push_str("## Skill: "); + skill_ctx.push_str(&skill.name); + skill_ctx.push('\n'); + skill_ctx.push_str(&skill.content); + } + } + } + } + } + if !skill_ctx.is_empty() { + // Log which skills matched + let matched: Vec<_> = registry + .find_matching(url_now, title_now, html) + .iter() + .map(|s| s.name.as_str()) + .collect(); + log::debug!( + "Injecting {} skills ({} chars): {:?}", + matched.len(), + skill_ctx.len(), + matched + ); + system_msg.push_str("\n\n---\nACTIVATED SKILLS:\n"); + system_msg.push_str(&skill_ctx); + } else if !registry.is_empty() { + log::debug!( + "No skills matched for url={} title={} html_len={}", + url_now, + title_now, + html.len() + ); + // Keep unmatched rounds lean: do not inject skill catalog text. + } + } + + // In extraction-only mode, inject schema / extraction prompt / relevance + // instructions into the system prompt (mirrors engine.rs system_prompt_compiled). + if effective_cfg.is_extraction_only() { + if let Some(schema) = &effective_cfg.extraction_schema { + system_msg.push_str("\n\n---\nExtraction Schema: "); + system_msg.push_str(&schema.name); + system_msg.push('\n'); + if let Some(desc) = &schema.description { + system_msg.push_str("Description: "); + system_msg.push_str(desc.trim()); + system_msg.push('\n'); + } + system_msg.push_str("The \"extracted\" field MUST conform to this JSON Schema:\n"); + system_msg.push_str(&schema.schema); + system_msg.push('\n'); + if schema.strict { + system_msg.push_str("STRICT MODE: Follow the schema exactly.\n"); + } + } + if let Some(extraction_prompt) = &effective_cfg.extraction_prompt { + system_msg.push_str("\nExtraction instructions: "); + system_msg.push_str(extraction_prompt.trim()); + system_msg.push('\n'); + } + if effective_cfg.relevance_gate { + system_msg.push_str( + "\n---\nRELEVANCE GATE: Include \"relevant\": true|false in your response.\n", + ); + if let Some(prompt) = &effective_cfg.relevance_prompt { + system_msg.push_str("Relevance criteria: "); + system_msg.push_str(prompt.trim()); + system_msg.push('\n'); + } else if let Some(ep) = &effective_cfg.extraction_prompt { + system_msg.push_str("Judge relevance against: "); + system_msg.push_str(ep.trim()); + system_msg.push('\n'); + } + } + } + + if screenshot.is_empty() { + Self::apply_text_only_prompt_flavor(&mut system_msg, self.has_dual_model_routing()); + } + + // Build user prompt + let user_text = self.build_user_prompt( + effective_cfg, + cap, + url_input, + url_now, + title_now, + html, + round_idx, + stagnated, + action_stuck_rounds, + loop_blocklist, + memory, + user_message_extra, + ); + + // Build user content – omit image_url block when text-only + let user_content = if use_vision && !screenshot.is_empty() { + serde_json::json!([ + { "type": "text", "text": user_text }, + { + "type": "image_url", + "image_url": { "url": screenshot } + } + ]) + } else { + serde_json::json!([ + { "type": "text", "text": user_text } + ]) + }; + + // Resolve model endpoint — use override (from fallback retry) or standard routing + let owned_override: Option<(String, String, Option)> = model_override + .map(|(u, m, k)| (u.to_string(), m.to_string(), k.map(|s| s.to_string()))); + let (resolved_api_url, resolved_model, resolved_api_key): (&str, &str, Option<&str>) = + if let Some((ref u, ref m, ref k)) = owned_override { + (u.as_str(), m.as_str(), k.as_deref()) + } else { + self.resolve_model_for_round_with_complexity( + use_vision, + &user_text, + html.len(), + round_idx, + stagnated, + ) + }; + + let is_anthropic = is_anthropic_endpoint(resolved_api_url); + let thinking_pl = if is_anthropic { + effective_thinking_payload(effective_cfg) + } else { + None + }; + let has_thinking = thinking_pl.is_some(); + + let messages = if is_anthropic { + vec![Message { + role: "user".to_string(), + content: user_content, + }] + } else { + vec![ + Message { + role: "system".to_string(), + content: serde_json::Value::String(system_msg.clone()), + }, + Message { + role: "user".to_string(), + content: user_content, + }, + ] + }; + + let use_tools = !effective_cfg.is_extraction_only() + && effective_cfg + .tool_calling_mode + .should_use_tools(resolved_model); + + let response_format = if is_anthropic || has_thinking || use_tools { + None + } else if effective_cfg.request_json_object { + Some(serde_json::json!({ "type": "json_object" })) + } else { + None + }; + + let tools = if use_tools { + Some( + ActionToolSchemas::all() + .into_iter() + .filter_map(|tool| serde_json::to_value(tool).ok()) + .collect::>(), + ) + } else { + None + }; + + let tool_choice = if use_tools { + Some(serde_json::json!("auto")) + } else { + None + }; + + let max_tokens = if let Some(budget) = thinking_pl + .as_ref() + .and_then(|v| v.get("budget_tokens")) + .and_then(|v| v.as_u64()) + { + Some(effective_cfg.max_tokens as u32 + budget as u32) + } else { + Some(effective_cfg.max_tokens as u32) + }; + + let request = Request { + model: resolved_model.to_string(), + messages, + temperature: if has_thinking { + None + } else { + Some(effective_cfg.temperature) + }, + max_tokens, + response_format, + reasoning: if is_anthropic { + None + } else { + reasoning_payload(effective_cfg) + }, + thinking: thinking_pl, + system: if is_anthropic { Some(system_msg) } else { None }, + tools, + tool_choice, + }; + + // Acquire semaphore if configured + let _permit = self.acquire_llm_permit().await; + + // Make HTTP request with 2 minute timeout for LLM calls + static CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(|| { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()) + }); + + let mut req = CLIENT.post(resolved_api_url).json(&request); + if let Some(key) = resolved_api_key { + req = req.bearer_auth(key); + } + + let resp = req.send().await?; + let status = resp.status(); + let body: serde_json::Value = resp.json().await?; + + if !status.is_success() { + return Err(EngineError::RemoteStatus( + status.as_u16(), + format!( + "API error {} (model={}): {}", + status, + resolved_model, + serde_json::to_string_pretty(&body).unwrap_or_default() + ), + )); + } + + // Extract content and usage + let content = extract_assistant_content(&body).unwrap_or_default(); + if !content.is_empty() { + log::debug!("LLM response content: {}", content); + } + let usage = extract_usage(&body); + + let tool_steps = if use_tools { + let tool_calls = parse_tool_calls(&body); + if !tool_calls.is_empty() { + tool_calls_to_steps(&tool_calls) + } else { + Vec::new() + } + } else { + Vec::new() + }; + + if content.trim().is_empty() && tool_steps.is_empty() { + return Err(EngineError::MissingField("choices[0].message.content")); + } + + // Parse JSON response + let parsed = if content.trim().is_empty() { + serde_json::json!({ + "label": "automation", + "done": false, + "steps": [] + }) + } else if effective_cfg.best_effort_json_extract { + best_effort_parse_json_object(&content)? + } else { + serde_json::from_str(&content)? + }; + + // Extract plan fields + let label = parsed + .get("label") + .and_then(|v| v.as_str()) + .unwrap_or("automation") + .to_string(); + + let done = parsed + .get("done") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let mut steps = parsed + .get("steps") + .and_then(|v| v.as_array()) + .cloned() + .unwrap_or_default(); + if !tool_steps.is_empty() { + steps.extend(tool_steps); + } + log::debug!( + "Parsed plan - label: {}, done: {}, steps: {:?}", + label, + done, + steps + ); + + // Extract relevance field if gate is enabled + let relevant = if effective_cfg.relevance_gate { + Some( + parsed + .get("relevant") + .and_then(|v| v.as_bool()) + .unwrap_or(true), + ) + } else { + None + }; + // Reasoning: prefer API-level thinking content (Anthropic/OpenAI thinking blocks), + // fall back to JSON-level "reasoning" field from the model's response. + let reasoning = extract_thinking_content(&body).or_else(|| { + parsed.get("reasoning").and_then(|v| { + if let Some(s) = v.as_str() { + let trimmed = s.trim(); + return if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + }; + } + if v.is_null() { + None + } else { + Some(v.to_string()) + } + }) + }); + + // Try to get extracted field, or fallback to the entire response when in extraction mode. + // Treat `extracted: {}` (empty object) the same as missing for recovery purposes. + let raw_extracted = parsed.get("extracted").cloned().and_then(|v| { + if v.as_object().is_some_and(|o| o.is_empty()) { + None // empty object → trigger fallback chain + } else { + Some(v) + } + }); + + let extracted = raw_extracted.or_else(|| { + // If no explicit "extracted" field but response looks like extracted data + // (has no standard automation fields), use the whole response + if parsed.get("label").is_none() + && parsed.get("done").is_none() + && parsed.get("steps").is_none() + { + // Response doesn't have automation structure, treat as direct extraction + Some(parsed.clone()) + } else if effective_cfg.extra_ai_data { + // In extraction mode, if response has automation structure but no extracted, + // check if there's any non-automation data to extract + let mut extracted_data = serde_json::Map::new(); + if let Some(obj) = parsed.as_object() { + for (key, value) in obj { + // Skip known automation fields + if !matches!( + key.as_str(), + "label" + | "done" + | "steps" + | "memory_ops" + | "extracted" + | "relevant" + | "reasoning" + ) { + extracted_data.insert(key.clone(), value.clone()); + } + } + } + if !extracted_data.is_empty() { + Some(serde_json::Value::Object(extracted_data)) + } else { + // Last resort: recover data from Fill steps. + // Weak models sometimes emit Fill actions instead of extracted data. + let mut fill_data = serde_json::Map::new(); + for step in &steps { + if let Some(fill) = step.get("Fill") { + if let (Some(sel), Some(val)) = ( + fill.get("selector").and_then(|s| s.as_str()), + fill.get("value"), + ) { + // Use the selector (or last segment) as key + let key = sel + .rsplit_once(' ') + .map(|(_, last)| last) + .unwrap_or(sel) + .trim_start_matches('#') + .trim_start_matches('.'); + if !key.is_empty() { + fill_data.insert(key.to_string(), val.clone()); + } + } + } + } + if !fill_data.is_empty() { + log::debug!("Recovered {} fields from Fill steps", fill_data.len()); + Some(serde_json::Value::Object(fill_data)) + } else { + None + } + } + } else { + None + } + }); + + // Parse memory operations + let memory_ops = parsed + .get("memory_ops") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|op| serde_json::from_value(op.clone()).ok()) + .collect() + }) + .unwrap_or_default(); + + Ok(AutomationPlan { + label, + done, + steps, + extracted, + memory_ops, + usage, + relevant, + reasoning, + }) + } + + // ── Chrome built-in AI inference ────────────────────────────────── + + /// Warm up Chrome's built-in LanguageModel (Gemini Nano). + /// + /// Creates a session and sends a trivial prompt to ensure the model + /// is loaded and ready. Mirrors `warm_gemini_model` from solvers.rs. + async fn warm_chrome_ai(page: &Page) -> EngineResult<()> { + use chromiumoxide::cdp::js_protocol::runtime::EvaluateParams; + + // No try/catch — errors propagate to Rust so we can detect missing API. + // First checks typeof to give a clear error before attempting .create(). + let js = r#"(async()=>{if(typeof LanguageModel==="undefined")throw new ReferenceError("LanguageModel is not defined");const s=await LanguageModel.create({expectedInputs:[{type:"text",languages:["en"]}],expectedOutputs:[{type:"text",languages:["en"]}]});const r=await s.prompt([{role:"user",content:[{type:"text",value:"ping"}]}]);return "ok:"+r.slice(0,20)})()"#; + + let params = EvaluateParams::builder() + .expression(js) + .await_promise(true) + .build() + .map_err(|e| { + EngineError::Remote(format!("warm_chrome_ai: build params failed: {e}")) + })?; + + match tokio::time::timeout(std::time::Duration::from_secs(60), page.evaluate(params)).await + { + Ok(Ok(_)) => { + log::info!("Chrome AI (LanguageModel) warm-up successful"); + Ok(()) + } + Ok(Err(e)) => { + let msg = format!("{e}"); + if Self::is_chrome_ai_missing(&msg) { + log::warn!("Chrome AI not available: {msg}"); + Err(EngineError::Remote( + "Chrome LanguageModel is not available. Enable chrome://flags/#optimization-guide-on-device-model and chrome://flags/#prompt-api-for-gemini-nano".to_string(), + )) + } else { + log::warn!("Chrome AI warm-up error (non-fatal): {msg}"); + Ok(()) + } + } + Err(_) => { + log::warn!("Chrome AI warm-up timed out (60s) — continuing anyway"); + Ok(()) + } + } + } + + /// Check if an error message indicates Chrome's LanguageModel is unavailable. + fn is_chrome_ai_missing(err: &str) -> bool { + err.contains("LanguageModel is not defined") + || err.contains("ReferenceError") + || err.contains("Uncaught ReferenceError") + || err.contains("cannot read property 'create' of undefined") + } + + /// Invalidate the cached Chrome AI session on the page. + /// + /// Called after errors or timeouts so the next retry creates a fresh session + /// rather than reusing one that may be in a broken state (e.g. after navigation). + async fn invalidate_chrome_ai_session(page: &Page) { + use chromiumoxide::cdp::js_protocol::runtime::EvaluateParams; + if let Ok(params) = EvaluateParams::builder() + .expression( + "try{if(window.__spiderSession){window.__spiderSession.destroy();}window.__spiderSession=null;window.__spiderSessionHash=0;}catch(e){}", + ) + .build() + { + if let Err(e) = page.evaluate(params).await { + log::debug!("Chrome AI session invalidation failed: {e}"); + } + } + } + + /// Infer plan via Chrome AI with retry policy. + /// + /// Same retry/backoff logic as `infer_plan_with_retry`, but delegates + /// to `infer_plan_chrome_ai_once` for in-page inference. + #[allow(clippy::too_many_arguments)] + async fn infer_plan_chrome_ai_with_retry( + &self, + page: &Page, + effective_cfg: &RemoteMultimodalConfig, + cap: &CaptureProfile, + url_input: &str, + url_now: &str, + title_now: &str, + html: &str, + screenshot: &str, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + loop_blocklist: &[String], + memory: Option<&AutomationMemory>, + base_system_prompt: Option<&str>, + system_prompt_extra: Option<&str>, + user_message_extra: Option<&str>, + ) -> EngineResult { + let max_attempts = effective_cfg.retry.max_attempts.max(1); + let mut last_err = None; + + for attempt in 0..max_attempts { + match self + .infer_plan_chrome_ai_once( + page, + effective_cfg, + cap, + url_input, + url_now, + title_now, + html, + screenshot, + round_idx, + stagnated, + action_stuck_rounds, + loop_blocklist, + memory, + base_system_prompt, + system_prompt_extra, + user_message_extra, + ) + .await + { + Ok(plan) => return Ok(plan), + Err(e) => { + last_err = Some(e); + if attempt + 1 < max_attempts { + let power = attempt.min(6); + let delay = (effective_cfg.retry.backoff_ms * (1 << power)).min(60_000); + tokio::time::sleep(std::time::Duration::from_millis(delay)).await; + } + } + } + } + + Err(last_err + .unwrap_or_else(|| EngineError::Remote("chrome_ai: max retries exceeded".to_string()))) + } + + /// Single plan inference via Chrome's built-in LanguageModel API. + /// + /// Uses a compact `CHROME_AI_SYSTEM_PROMPT`, passes it via the + /// `systemPrompt` parameter in `LanguageModel.create()` for proper role + /// separation, and reuses the session across rounds via a JS global. + #[allow(clippy::too_many_arguments)] + async fn infer_plan_chrome_ai_once( + &self, + page: &Page, + effective_cfg: &RemoteMultimodalConfig, + cap: &CaptureProfile, + url_input: &str, + url_now: &str, + title_now: &str, + html: &str, + screenshot: &str, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + loop_blocklist: &[String], + memory: Option<&AutomationMemory>, + base_system_prompt: Option<&str>, + system_prompt_extra: Option<&str>, + user_message_extra: Option<&str>, + ) -> EngineResult { + use super::{ + best_effort_parse_json_object, fnv1a64, CHROME_AI_SYSTEM_PROMPT, + EXTRACTION_ONLY_SYSTEM_PROMPT, + }; + use chromiumoxide::cdp::js_protocol::runtime::EvaluateParams; + + // ── Build system prompt (compact Chrome AI variant) ── + let mut system_msg = if effective_cfg.is_extraction_only() { + EXTRACTION_ONLY_SYSTEM_PROMPT.to_string() + } else { + CHROME_AI_SYSTEM_PROMPT.to_string() + }; + if let Some(base) = base_system_prompt { + if !base.trim().is_empty() { + system_msg.push_str("\n\n---\nCONFIGURED SYSTEM INSTRUCTIONS:\n"); + system_msg.push_str(base.trim()); + } + } + if let Some(extra) = system_prompt_extra { + if !extra.trim().is_empty() { + system_msg.push_str("\n\n---\nADDITIONAL INSTRUCTIONS:\n"); + system_msg.push_str(extra.trim()); + } + } + + // Skill injection + #[cfg(feature = "skills")] + if let Some(ref registry) = self.skill_registry { + let mut skill_ctx = registry.match_context_limited( + url_now, + title_now, + html, + effective_cfg.max_skills_per_round, + effective_cfg.max_skill_context_chars, + ); + if let Some(mem) = memory { + if let Some(active) = mem.get("_active_skill") { + if let Some(name) = active.as_str() { + if let Some(skill) = registry.get(name) { + if !skill_ctx.contains(&skill.name) { + if !skill_ctx.is_empty() { + skill_ctx.push_str("\n\n"); + } + skill_ctx.push_str("## Skill: "); + skill_ctx.push_str(&skill.name); + skill_ctx.push('\n'); + skill_ctx.push_str(&skill.content); + } + } + } + } + } + if !skill_ctx.is_empty() { + system_msg.push_str("\n\n---\nACTIVATED SKILLS:\n"); + system_msg.push_str(&skill_ctx); + } + } + + // Text-only flavor when no screenshot + if screenshot.is_empty() { + Self::apply_text_only_prompt_flavor(&mut system_msg, false); + } + + // When stuck for 5+ rounds, destroy the LM session so the model + // restarts with a fresh system prompt. Prevents infinite loops. + if action_stuck_rounds >= 5 { + Self::invalidate_chrome_ai_session(page).await; + } + + // ── Probe interactive elements for small-model guidance ── + // Annotates visible interactive elements with `data-spider-idx` attributes + // and returns a numbered list. The model clicks by index (e.g. [0], [1]) + // instead of guessing CSS selectors — much more reliable for small models. + let interactive_hint = { + let probe_js = r#"(()=>{try{document.querySelectorAll('[data-spider-idx]').forEach(el=>el.removeAttribute('data-spider-idx'));const seen=new Set();const items=[];let idx=0;function add(el,skipEmptyLinks){if(idx>=15||seen.has(el))return;const r=el.getBoundingClientRect();if(r.width<1||r.height<1)return;if(el.closest('footer,nav,.footer,.nav,.site-footer,.site-header,header'))return;seen.add(el);const tag=el.tagName.toLowerCase();const ty=el.getAttribute('type')||'';const role=el.getAttribute('role')||'';const txt=(el.textContent||el.getAttribute('aria-label')||el.getAttribute('placeholder')||'').trim().replace(/\s+/g,' ').slice(0,40);if(tag==='a'){if(!txt||skipEmptyLinks)return;const h=el.getAttribute('href')||'';if(h&&!h.startsWith('#')&&!h.startsWith('javascript')&&new URL(h,location.href).pathname!==location.pathname)return;}el.setAttribute('data-spider-idx',String(idx));const desc=role||ty||(tag==='a'?'link':tag);items.push('['+idx+'] '+desc+(txt?' "'+txt.replace(/"/g,"'")+'"':''));idx++;}const hi=document.querySelectorAll('button,input,select,textarea,label,[role="button"],[role="checkbox"],[role="link"],[onclick]');for(const el of hi)add(el,false);const links=document.querySelectorAll('a');for(const el of links)add(el,true);const all=document.querySelectorAll('div,span,li');for(const el of all){if(idx>=15)break;if(seen.has(el))continue;try{if(getComputedStyle(el).cursor==='pointer'){add(el,false);}}catch(e){}}return items.join('\n');}catch(e){return'';}})();"#; + let probe_params = match EvaluateParams::builder().expression(probe_js).build() { + Ok(p) => p, + Err(_) => { + return Err(EngineError::Remote( + "chrome_ai: failed to build probe params".to_string(), + )); + } + }; + match page.evaluate(probe_params).await { + Ok(eval) => eval + .value() + .and_then(|v| v.as_str().map(|s| s.to_string())) + .unwrap_or_default(), + Err(_) => String::new(), + } + }; + + if !interactive_hint.is_empty() { + log::debug!("Chrome AI element probe found:\n{}", interactive_hint); + } + + // ── Build user prompt (reuse existing method) ── + let mut user_text = self.build_user_prompt( + effective_cfg, + cap, + url_input, + url_now, + title_now, + html, + round_idx, + stagnated, + action_stuck_rounds, + loop_blocklist, + memory, + user_message_extra, + ); + + // Strip framework data-* attributes from user prompt to prevent small + // models from hallucinating selectors like [data-v-abc123] from raw HTML. + // Preserves our own data-spider-idx references in the CLICKABLE ELEMENTS section. + user_text = Self::strip_framework_data_attrs(&user_text); + + // Prepend indexed interactive elements list — model clicks by number + if !interactive_hint.is_empty() { + user_text = format!( + "CLICKABLE ELEMENTS (click by index, e.g. {{\"Click\":\"[data-spider-idx='0']\"}} ):\n{interactive_hint}\n\n{user_text}" + ); + } + + // Append a response primer to help smaller models produce valid JSON. + // This nudges the model to start its response with the expected format. + user_text.push_str("\nRespond with JSON only. Start with {\"label\":"); + + // ── Smart context budgeting ── + // System prompt goes to systemPrompt param (untruncated — it's compact). + // User prompt: truncate only the HTML section if over budget. + let max_user_chars = self.chrome_ai_max_user_chars; + let user_text = if user_text.len() > max_user_chars { + Self::truncate_chrome_ai_user_prompt(&user_text, max_user_chars) + } else { + user_text + }; + + // ── Hash system prompt for session reuse ── + let sys_hash = fnv1a64(system_msg.as_bytes()); + + // ── Build JavaScript for in-page inference with session reuse ── + let escaped_system = serde_json::to_string(&system_msg) + .unwrap_or_else(|_| format!("\"{}\"", system_msg.replace('\"', "\\\""))); + let escaped_user = serde_json::to_string(&user_text) + .unwrap_or_else(|_| format!("\"{}\"", user_text.replace('\"', "\\\""))); + + let has_screenshot = !screenshot.is_empty(); + + // Pass model parameters from config. + // Chrome LanguageModel API requires BOTH topK and temperature, or neither. + let temperature = effective_cfg.temperature; + let top_k = if temperature < 0.01 { 1 } else { 40 }; + + // JS template: try/catch around session creation and prompting. + // On error, destroy stale session so next retry starts fresh. + let js = if has_screenshot { + let escaped_screenshot = serde_json::to_string(&screenshot).unwrap_or_default(); + format!( + r#"(async()=>{{try{{ +const h={hash}; +if(!window.__spiderSession||window.__spiderSessionHash!==h){{ +window.__spiderSession=await LanguageModel.create({{ +systemPrompt:{system}, +temperature:{temperature},topK:{top_k}, +expectedInputs:[{{type:"text",languages:["en"]}},{{type:"image"}}], +expectedOutputs:[{{type:"text",languages:["en"]}}] +}}); +window.__spiderSessionHash=h; +}} +const s=window.__spiderSession; +const resp=await fetch({screenshot}); +const blob=await resp.blob(); +const msg=[{{role:"user",content:[{{type:"text",value:{user}}},{{type:"image",value:blob}}]}}]; +return await s.prompt(msg); +}}catch(e){{try{{window.__spiderSession?.destroy();}}catch(_){{}}window.__spiderSession=null;throw e;}} +}})()"#, + hash = sys_hash, + system = escaped_system, + temperature = temperature, + top_k = top_k, + screenshot = escaped_screenshot, + user = escaped_user, + ) + } else { + format!( + r#"(async()=>{{try{{ +const h={hash}; +if(!window.__spiderSession||window.__spiderSessionHash!==h){{ +window.__spiderSession=await LanguageModel.create({{ +systemPrompt:{system}, +temperature:{temperature},topK:{top_k}, +expectedInputs:[{{type:"text",languages:["en"]}}], +expectedOutputs:[{{type:"text",languages:["en"]}}] +}}); +window.__spiderSessionHash=h; +}} +const s=window.__spiderSession; +const msg=[{{role:"user",content:[{{type:"text",value:{user}}}]}}]; +return await s.prompt(msg); +}}catch(e){{try{{window.__spiderSession?.destroy();}}catch(_){{}}window.__spiderSession=null;throw e;}} +}})()"#, + hash = sys_hash, + system = escaped_system, + temperature = temperature, + top_k = top_k, + user = escaped_user, + ) + }; + + // ── Evaluate on page ── + let params = EvaluateParams::builder() + .expression(&js) + .await_promise(true) + .build() + .map_err(|e| { + EngineError::Remote(format!("chrome_ai: build eval params failed: {e}")) + })?; + + let eval_result = + tokio::time::timeout(std::time::Duration::from_secs(120), page.evaluate(params)).await; + + let content = match eval_result { + Ok(Ok(eval)) => match eval.value() { + Some(serde_json::Value::String(s)) => s.to_string(), + Some(v) => v.to_string(), + None => { + return Err(EngineError::Remote( + "chrome_ai: empty response from LanguageModel".to_string(), + )); + } + }, + Ok(Err(e)) => { + let msg = format!("{e}"); + if Self::is_chrome_ai_missing(&msg) { + return Err(EngineError::Remote(format!( + "Chrome LanguageModel not available: {msg}. Enable chrome://flags/#optimization-guide-on-device-model and chrome://flags/#prompt-api-for-gemini-nano" + ))); + } + // Session may have been invalidated (e.g. navigation); clear it + Self::invalidate_chrome_ai_session(page).await; + return Err(EngineError::Remote(format!("chrome_ai: eval error: {msg}"))); + } + Err(_) => { + // Timeout — session likely stuck; clear for next attempt + Self::invalidate_chrome_ai_session(page).await; + return Err(EngineError::Remote( + "chrome_ai: inference timed out (120s)".to_string(), + )); + } + }; + + log::debug!("Chrome AI response: {}", content); + + // ── Parse response ── + let parsed = if effective_cfg.best_effort_json_extract { + best_effort_parse_json_object(&content)? + } else { + serde_json::from_str(&content)? + }; + + // Normalize: small models may return simplified formats without `steps` array. + // Convert {"action":"click","element":"sel"} → {"steps":[{"Click":"sel"}]} + let parsed = Self::normalize_chrome_ai_response(parsed); + + let label = parsed + .get("label") + .and_then(|v| v.as_str()) + .unwrap_or("automation") + .to_string(); + + let done = parsed + .get("done") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let steps = parsed + .get("steps") + .and_then(|v| v.as_array()) + .cloned() + .unwrap_or_default(); + + let relevant = if effective_cfg.relevance_gate { + Some( + parsed + .get("relevant") + .and_then(|v| v.as_bool()) + .unwrap_or(true), + ) + } else { + None + }; + + let reasoning = parsed.get("reasoning").and_then(|v| { + if let Some(s) = v.as_str() { + let trimmed = s.trim(); + return if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + }; + } + if v.is_null() { + None + } else { + Some(v.to_string()) + } + }); + + let raw_extracted = parsed.get("extracted").cloned().and_then(|v| { + if v.as_object().is_some_and(|o| o.is_empty()) { + None + } else { + Some(v) + } + }); + + let extracted = raw_extracted.or_else(|| { + if parsed.get("label").is_none() + && parsed.get("done").is_none() + && parsed.get("steps").is_none() + { + Some(parsed.clone()) + } else if effective_cfg.extra_ai_data { + let mut data = serde_json::Map::new(); + if let Some(obj) = parsed.as_object() { + for (key, value) in obj { + if !matches!( + key.as_str(), + "label" + | "done" + | "steps" + | "memory_ops" + | "extracted" + | "relevant" + | "reasoning" + ) { + data.insert(key.clone(), value.clone()); + } + } + } + if !data.is_empty() { + Some(serde_json::Value::Object(data)) + } else { + None + } + } else { + None + } + }); + + let memory_ops = parsed + .get("memory_ops") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|op| serde_json::from_value(op.clone()).ok()) + .collect() + }) + .unwrap_or_default(); + + Ok(AutomationPlan { + label, + done, + steps, + extracted, + memory_ops, + usage: AutomationUsage { + llm_calls: 1, + ..AutomationUsage::default() + }, + relevant, + reasoning, + }) + } + + /// Truncate a Chrome AI user prompt by shrinking the HTML context section. + /// + /// Finds the `HTML CONTEXT:\n` marker and truncates only the HTML portion, + /// preserving everything after it (task instructions, memory, etc.). + fn truncate_chrome_ai_user_prompt(user_text: &str, max_chars: usize) -> String { + const HTML_MARKER: &str = "HTML CONTEXT:\n"; + if let Some(html_start) = user_text.find(HTML_MARKER) { + let html_content_start = html_start + HTML_MARKER.len(); + // Find end of HTML section — look for next section marker + let after_html = &user_text[html_content_start..]; + let html_end = after_html + .find("\n\nUSER INSTRUCTIONS:") + .or_else(|| after_html.find("\n\nTASK:")) + .or_else(|| after_html.find("\n\nMEMORY:")) + .map(|pos| html_content_start + pos) + .unwrap_or(user_text.len()); + + let before_html = &user_text[..html_content_start]; + let html_section = &user_text[html_content_start..html_end]; + let after_section = &user_text[html_end..]; + + let non_html_len = before_html.len() + after_section.len(); + if non_html_len >= max_chars { + // Even without HTML we're over budget — truncate from tail + return truncate_utf8_tail(user_text, max_chars); + } + let html_budget = max_chars - non_html_len; + let truncated_html = truncate_utf8_tail(html_section, html_budget); + format!("{before_html}{truncated_html}{after_section}") + } else { + // No HTML section found — truncate from tail as fallback + truncate_utf8_tail(user_text, max_chars) + } + } + + /// Strip framework data-* attributes from text to prevent small models from + /// hallucinating selectors like `[data-v-abc123]` from raw HTML content. + /// + /// Removes patterns like `data-v-XXXXX=""` and `data-reactid="N"` that + /// Vue.js, React, and Angular inject into the DOM. Preserves `data-spider-idx`. + fn strip_framework_data_attrs(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + let mut i = 0; + let bytes = text.as_bytes(); + let len = bytes.len(); + + while i < len { + // Look for "data-" pattern + if i + 5 <= len && text.get(i..i + 5) == Some("data-") { + // Don't strip our own data-spider-idx + if i + 15 <= len && text.get(i..i + 15) == Some("data-spider-idx") { + result.push_str("data-spider-idx"); + i += 15; + continue; + } + // Check for framework patterns: data-v-, data-reactid, data-react-, data-ng- + let rest = &text[i + 5..]; + let is_framework = rest.starts_with("v-") + || rest.starts_with("react") + || rest.starts_with("ng-") + || rest.starts_with("testid"); + if is_framework { + // Skip the entire attribute: data-xxx="..." or data-xxx='' or data-xxx + let attr_start = i; + // Skip attribute name + while i < len && bytes[i] != b'=' && bytes[i] != b' ' && bytes[i] != b'>' { + i += 1; + } + // Skip ="value" if present + if i < len && bytes[i] == b'=' { + i += 1; // skip = + if i < len && (bytes[i] == b'"' || bytes[i] == b'\'') { + let quote = bytes[i]; + i += 1; // skip opening quote + while i < len && bytes[i] != quote { + i += 1; + } + if i < len { + i += 1; // skip closing quote + } + } + } + // Skip trailing whitespace if the attr was preceded by a space + if attr_start > 0 + && text.as_bytes()[attr_start - 1] == b' ' + && i < len + && bytes[i] == b' ' + { + i += 1; + } + continue; + } + } + if let Some(ch) = text.get(i..).and_then(|s| s.chars().next()) { + result.push(ch); + i += ch.len_utf8(); + } else { + i += 1; + } + } + + result + } + + /// Normalize Chrome AI responses from small models that may not follow the + /// exact output schema. + /// + /// Handles common simplified formats: + /// - `{"action":"click","element":"sel"}` → `{"label":"click sel","done":false,"steps":[{"Click":"sel"}]}` + /// - `{"action":"fill","element":"sel","value":"text"}` → `{"steps":[{"Fill":{"selector":"sel","value":"text"}}]}` + /// - `{"action":"scroll","value":300}` → `{"steps":[{"ScrollY":300}]}` + /// - `{"action":"navigate","url":"..."}` → `{"steps":[{"Navigate":"..."}]}` + fn normalize_chrome_ai_response(parsed: serde_json::Value) -> serde_json::Value { + // If it already has a `steps` array, return as-is + if parsed.get("steps").and_then(|v| v.as_array()).is_some() { + return parsed; + } + + let obj = match parsed.as_object() { + Some(o) => o, + None => return parsed, + }; + + // Check if any value is a step-like object: {"Click":"sel"}, {"Fill":{...}}, etc. + // Small models sometimes wrap steps like: {"action": {"Click":"[0]"}, "label":"..."} + static KNOWN_ACTIONS: &[&str] = &[ + "Click", + "ClickPoint", + "ClickAll", + "Fill", + "Press", + "ScrollY", + "ScrollTo", + "Navigate", + "Wait", + "WaitFor", + "Evaluate", + "SetViewport", + "ClickDragPoint", + ]; + for (_key, val) in obj { + if let Some(step_obj) = val.as_object() { + if step_obj.len() == 1 { + if let Some(action_name) = step_obj.keys().next() { + if KNOWN_ACTIONS.iter().any(|a| a == action_name) { + // Found an embedded step — normalize index references in the value + let mut step = val.clone(); + if let Some(inner) = + step.as_object_mut().and_then(|o| o.get_mut(action_name)) + { + if let Some(s) = inner.as_str() { + let t = s.trim().trim_start_matches('[').trim_end_matches(']'); + if !t.is_empty() + && t.len() <= 2 + && t.chars().all(|c| c.is_ascii_digit()) + { + *inner = + serde_json::json!(format!("[data-spider-idx='{t}']")); + } + } + } + let label = obj + .get("label") + .and_then(|v| v.as_str()) + .unwrap_or("automation"); + let done = obj.get("done").and_then(|v| v.as_bool()).unwrap_or(false); + log::debug!("Normalized Chrome AI response: embedded step {:?}", step); + return serde_json::json!({ + "label": label, + "done": done, + "steps": [step], + }); + } + } + } + } + } + + // Extract simplified fields — small models use various field names + let mut action = obj + .get("action") + .or_else(|| obj.get("action_type")) + .or_else(|| obj.get("type")) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_lowercase(); + + // Infer action from "label" when no explicit action field is present. + // Small models often put the action name in "label": "Click", "Scroll", etc. + if action.is_empty() { + if let Some(label) = obj.get("label").and_then(|v| v.as_str()) { + let lower = label.to_lowercase(); + if lower.starts_with("click") || lower.starts_with("tap") { + action = "click".to_string(); + } else if lower.starts_with("evaluat") { + action = "evaluate".to_string(); + } else if lower.starts_with("scroll") { + action = "scroll".to_string(); + } else if lower.starts_with("fill") || lower.starts_with("type") { + action = "fill".to_string(); + } else if lower.starts_with("press") || lower.starts_with("key") { + action = "press".to_string(); + } else if lower.starts_with("wait") { + action = "wait".to_string(); + } else if lower.starts_with("navigate") || lower.starts_with("go") { + action = "navigate".to_string(); + } + } + } + + // Helper: check if a string looks like a CSS selector or index reference + fn looks_like_selector(s: &str) -> bool { + let t = s.trim(); + t.starts_with('.') + || t.starts_with('#') + || t.starts_with('[') + || t.contains("data-spider-idx") + } + // Extract a numeric index from strings like "0", "[0]", "[0] link", etc. + fn extract_index_from_str(s: &str) -> Option { + let t = s.trim(); + // Direct number: "0", "12" + if let Ok(n) = t.parse::() { + return Some(n); + } + // Bracketed: "[0]", "[0] link", "[12] button" + if t.starts_with('[') { + if let Some(end) = t.find(']') { + let inner = &t[1..end]; + if let Ok(n) = inner.trim().parse::() { + return Some(n); + } + } + } + None + } + + // Helper: extract a numeric index from any JSON value. + // Handles: 0, "0", "[0]", "[0] link", [0], ["0"] + fn extract_index(v: &serde_json::Value) -> Option { + if let Some(n) = v.as_u64() { + return Some(n); + } + if let Some(s) = v.as_str() { + return extract_index_from_str(s); + } + // Array with a single element: [0] → 0, ["0"] → 0 + if let Some(arr) = v.as_array() { + if arr.len() == 1 { + if let Some(n) = arr[0].as_u64() { + return Some(n); + } + if let Some(s) = arr[0].as_str() { + return extract_index_from_str(s); + } + } + } + None + } + + // Search for element/index across ALL values in the response. + // Small models use arbitrary field names — we scan everything. + let raw_element = { + let mut found = String::new(); + + // 1. Scan all values: prefer selectors, then indexes, then bare names + for (key, val) in obj { + // Skip metadata fields + if matches!( + key.as_str(), + "label" + | "action" + | "action_type" + | "type" + | "done" + | "extracted" + | "memory_ops" + | "reasoning" + ) { + continue; + } + + // Check for selector-like strings + if let Some(s) = val.as_str() { + if looks_like_selector(s) { + found = s.to_string(); + break; + } + } + + // Check for index values (number, string, or array) + if let Some(idx) = extract_index(val) { + if idx < 100 { + found = format!("[data-spider-idx='{idx}']"); + break; + } + } + + // Check for bare element names (strings with hyphens) + if found.is_empty() { + if let Some(s) = val.as_str() { + if !s.is_empty() && s.contains('-') && !s.contains(' ') { + found = s.to_string(); + // Don't break — keep looking for better matches + } + } + } + } + + // Convert index-like string values to data-spider-idx selectors + if let Some(idx) = extract_index_from_str(&found) { + format!("[data-spider-idx='{idx}']") + } else { + found + } + }; + + let raw_element = raw_element.as_str(); + + // If the element looks like a bare name (no CSS prefix), try both class and ID. + // Small models often output "my-class" instead of ".my-class" or "#my-class". + // CSS comma selectors let querySelector match whichever exists. + let element = if !raw_element.is_empty() + && !raw_element.starts_with('.') + && !raw_element.starts_with('#') + && !raw_element.starts_with('[') + && !raw_element.contains(' ') + && !raw_element.contains('>') + && !raw_element.contains(':') + && !raw_element.contains('/') + && raw_element.contains('-') + { + format!(".{raw_element}, #{raw_element}") + } else { + raw_element.to_string() + }; + let element = element.as_str(); + let value = obj.get("value"); + let url = obj.get("url").and_then(|v| v.as_str()).unwrap_or(""); + + if action.is_empty() && element.is_empty() { + return parsed; + } + + // Build the step + let step: serde_json::Value = match action.as_str() { + "click" | "tap" if !element.is_empty() => { + serde_json::json!({ "Click": element }) + } + "fill" | "type" | "input" if !element.is_empty() => { + let val = value.and_then(|v| v.as_str()).unwrap_or(""); + serde_json::json!({ "Fill": { "selector": element, "value": val } }) + } + "scroll" | "scrolldown" | "scrollup" => { + let amount = value.and_then(|v| v.as_i64()).unwrap_or(300); + serde_json::json!({ "ScrollY": amount }) + } + "navigate" | "goto" if !url.is_empty() => { + serde_json::json!({ "Navigate": url }) + } + "wait" => { + let ms = value.and_then(|v| v.as_u64()).unwrap_or(1000); + serde_json::json!({ "Wait": ms }) + } + "press" => { + let key = value.and_then(|v| v.as_str()).unwrap_or("Enter"); + serde_json::json!({ "Press": key }) + } + // Evaluate: small models sometimes emit {"label":"Evaluate","selector":"..."} + // Build a simple DOM-read JS snippet or pass through the element as a selector read. + "evaluate" | "eval" => { + let js_code = if let Some(v) = value.and_then(|v| v.as_str()) { + v.to_string() + } else if !element.is_empty() { + format!("document.title=document.querySelector('{}')?.textContent?.trim()?.slice(0,200)||'empty'", element.replace('\'', "\\'")) + } else { + "document.title=document.body?.innerText?.slice(0,200)||'empty'".to_string() + }; + serde_json::json!({ "Evaluate": js_code }) + } + // If action is unrecognized but element is a valid selector, try Click + _ if !element.is_empty() => { + serde_json::json!({ "Click": element }) + } + _ => return parsed, + }; + + let label = obj + .get("label") + .or_else(|| obj.get("action_taken")) + .or_else(|| obj.get("description")) + .and_then(|v| v.as_str()) + .unwrap_or("automation"); + let done = obj.get("done").and_then(|v| v.as_bool()).unwrap_or(false); + + log::debug!( + "Normalized Chrome AI response: action={}, element={} → {:?}", + action, + element, + step + ); + + let mut result = serde_json::json!({ + "label": label, + "done": done, + "steps": [step], + }); + + // Preserve any extracted data + if let Some(obj_mut) = result.as_object_mut() { + if let Some(extracted) = obj.get("extracted") { + obj_mut.insert("extracted".to_string(), extracted.clone()); + } + if let Some(mem_ops) = obj.get("memory_ops") { + obj_mut.insert("memory_ops".to_string(), mem_ops.clone()); + } + } + + result + } + + /// Execute automation steps on the page. + /// + /// Handles WebAutomation enum-style actions like `{ "Click": "selector" }`. + /// Returns (steps_executed, spawn_pages, action_outcomes) where spawn_pages + /// contains URLs from `OpenPage` actions and action_outcomes provides + /// per-action feedback for the LLM. + async fn execute_steps( + &self, + page: &Page, + steps: &[serde_json::Value], + _cfg: &RemoteMultimodalConfig, + ) -> EngineResult<(usize, Vec, Vec)> { + let mut executed = 0; + let mut spawn_pages = Vec::new(); + let mut outcomes = Vec::new(); + + for step in steps { + log::debug!("Executing step: {:?}", step); + // Handle WebAutomation enum-style format: { "ActionName": value } + if let Some(obj) = step.as_object() { + for (action, value) in obj { + log::debug!("Action: {}, Value: {:?}", action, value); + // Handle OpenPage specially - collect URLs instead of executing + if action == "OpenPage" { + log::info!("OpenPage action detected: {:?}", value); + if let Some(url) = value.as_str() { + spawn_pages.push(url.to_string()); + executed += 1; + outcomes.push(ActionOutcome::ok("OpenPage")); + } else if let Some(urls) = value.as_array() { + // Support array of URLs: { "OpenPage": ["url1", "url2"] } + let capped = &urls[..urls.len().min(MAX_SPAWN_PAGES_PER_ACTION)]; + if urls.len() > MAX_SPAWN_PAGES_PER_ACTION { + log::warn!( + "OpenPage: truncated {} URLs to {}", + urls.len(), + MAX_SPAWN_PAGES_PER_ACTION + ); + } + for url_val in capped { + if let Some(url) = url_val.as_str() { + spawn_pages.push(url.to_string()); + } + } + executed += 1; + outcomes.push(ActionOutcome::ok("OpenPage")); + } + continue; + } + + let outcome = self.execute_single_action(page, action, value).await; + if outcome.success { + executed += 1; + } + outcomes.push(outcome); + } + } + } + + Ok((executed, spawn_pages, outcomes)) + } + + /// Execute a single WebAutomation action, returning a structured outcome. + async fn execute_single_action( + &self, + page: &Page, + action: &str, + value: &serde_json::Value, + ) -> ActionOutcome { + match action { + // === Click Actions === + "Click" => { + if let Some(selector) = value.as_str() { + match page.find_element(selector).await { + Ok(elem) => { + if elem.click().await.is_ok() { + return ActionOutcome::ok("Click"); + } + return ActionOutcome::fail("Click", "click event failed"); + } + Err(_) => { + return ActionOutcome::fail( + "Click", + format!("selector not found: {}", truncate_utf8_tail(selector, 80)), + ); + } + } + } + ActionOutcome::fail("Click", "missing or invalid selector") + } + "ClickAll" => { + if let Some(selector) = value.as_str() { + match page.find_elements(selector).await { + Ok(elements) => { + for elem in elements { + let _ = elem.click().await; + } + return ActionOutcome::ok("ClickAll"); + } + Err(_) => { + return ActionOutcome::fail( + "ClickAll", + format!("selector not found: {}", truncate_utf8_tail(selector, 80)), + ); + } + } + } + ActionOutcome::fail("ClickAll", "missing or invalid selector") + } + "ClickPoint" => { + let x = value.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = value.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + // Move mouse first to set hover target, then click + let point = Point::new(x, y); + let _ = page.move_mouse_smooth(point).await; + match page.click(point).await { + Ok(_) => ActionOutcome::ok("ClickPoint"), + Err(_) => { + ActionOutcome::fail("ClickPoint", format!("CDP click failed at ({x}, {y})")) + } + } + } + "ClickHold" => { + let selector = value.get("selector").and_then(|v| v.as_str()); + let hold_ms = value + .get("hold_ms") + .and_then(|v| v.as_u64()) + .unwrap_or(500) + .min(MAX_HOLD_MS); + if let Some(sel) = selector { + if let Ok(elem) = page.find_element(sel).await { + if let Ok(sv) = elem.scroll_into_view().await { + if let Ok(point) = sv.clickable_point().await { + let _ = page.move_mouse_smooth(point).await; + let _ = page + .click_and_hold( + point, + std::time::Duration::from_millis(hold_ms), + ) + .await; + return ActionOutcome::ok("ClickHold"); + } + } + } + return ActionOutcome::fail( + "ClickHold", + format!( + "selector not found or not clickable: {}", + truncate_utf8_tail(sel, 80) + ), + ); + } + ActionOutcome::fail("ClickHold", "missing selector") + } + "ClickHoldPoint" => { + let x = value.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = value.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let hold_ms = value + .get("hold_ms") + .and_then(|v| v.as_u64()) + .unwrap_or(500) + .min(MAX_HOLD_MS); + let point = Point::new(x, y); + let _ = page.move_mouse_smooth(point).await; + match page + .click_and_hold(point, std::time::Duration::from_millis(hold_ms)) + .await + { + Ok(_) => ActionOutcome::ok("ClickHoldPoint"), + Err(e) => { + ActionOutcome::fail("ClickHoldPoint", format!("CDP hold failed: {e}")) + } + } + } + "DoubleClick" => { + if let Some(selector) = value.as_str() { + if let Ok(elem) = page.find_element(selector).await { + if let Ok(sv) = elem.scroll_into_view().await { + if let Ok(point) = sv.clickable_point().await { + let _ = page.move_mouse_smooth(point).await; + let _ = page.double_click(point).await; + return ActionOutcome::ok("DoubleClick"); + } + } + } + return ActionOutcome::fail( + "DoubleClick", + format!( + "selector not found or not clickable: {}", + truncate_utf8_tail(selector, 80) + ), + ); + } + ActionOutcome::fail("DoubleClick", "missing selector") + } + "DoubleClickPoint" => { + let x = value.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = value.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let point = Point::new(x, y); + let _ = page.move_mouse_smooth(point).await; + match page.double_click(point).await { + Ok(_) => ActionOutcome::ok("DoubleClickPoint"), + Err(e) => ActionOutcome::fail( + "DoubleClickPoint", + format!("CDP double-click failed: {e}"), + ), + } + } + "RightClick" => { + if let Some(selector) = value.as_str() { + if let Ok(elem) = page.find_element(selector).await { + if let Ok(sv) = elem.scroll_into_view().await { + if let Ok(point) = sv.clickable_point().await { + let _ = page.move_mouse_smooth(point).await; + let _ = page.right_click(point).await; + return ActionOutcome::ok("RightClick"); + } + } + } + return ActionOutcome::fail( + "RightClick", + format!( + "selector not found or not clickable: {}", + truncate_utf8_tail(selector, 80) + ), + ); + } + ActionOutcome::fail("RightClick", "missing selector") + } + "RightClickPoint" => { + let x = value.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = value.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let point = Point::new(x, y); + let _ = page.move_mouse_smooth(point).await; + match page.right_click(point).await { + Ok(_) => ActionOutcome::ok("RightClickPoint"), + Err(e) => ActionOutcome::fail( + "RightClickPoint", + format!("CDP right-click failed: {e}"), + ), + } + } + "ClickAllClickable" => { + if let Ok(elements) = page + .find_elements(r#"a, button, [onclick], [role="button"]"#) + .await + { + for elem in elements { + let _ = elem.click().await; + } + ActionOutcome::ok("ClickAllClickable") + } else { + ActionOutcome::fail("ClickAllClickable", "find_elements failed") + } + } + + // === Drag Actions === + "ClickDrag" => { + let from = value.get("from").and_then(|v| v.as_str()); + let to = value.get("to").and_then(|v| v.as_str()); + if let (Some(from_sel), Some(to_sel)) = (from, to) { + if let Ok(from_elem) = page.find_element(from_sel).await { + if let Ok(from_sv) = from_elem.scroll_into_view().await { + if let Ok(from_point) = from_sv.clickable_point().await { + if let Ok(to_elem) = page.find_element(to_sel).await { + if let Ok(to_sv) = to_elem.scroll_into_view().await { + if let Ok(to_point) = to_sv.clickable_point().await { + let _ = page + .click_and_drag_smooth(from_point, to_point) + .await; + return ActionOutcome::ok("ClickDrag"); + } + } + } + } + } + } + return ActionOutcome::fail("ClickDrag", "one or both selectors not found"); + } + ActionOutcome::fail("ClickDrag", "missing from/to selectors") + } + "ClickDragPoint" => { + let from_x = value.get("from_x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let from_y = value.get("from_y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let to_x = value.get("to_x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let to_y = value.get("to_y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let _ = page + .click_and_drag_smooth(Point::new(from_x, from_y), Point::new(to_x, to_y)) + .await; + ActionOutcome::ok("ClickDragPoint") + } + + // === Input Actions === + "Fill" => { + let selector = value.get("selector").and_then(|v| v.as_str()); + let text = value.get("value").and_then(|v| v.as_str()); + if let (Some(sel), Some(txt)) = (selector, text) { + match page.find_element(sel).await { + Ok(elem) => { + let _ = elem.click().await; + // Clear existing value before typing + if let Err(e) = eval_with_timeout( + page, + format!("document.querySelector({}).value = ''", js_escape(sel)), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("Fill", format!("clear failed: {}", e)); + } + if let Err(e) = elem.type_str(txt).await { + return ActionOutcome::fail( + "Fill", + format!("type_str failed: {}", e), + ); + } + return ActionOutcome::ok("Fill"); + } + Err(_) => { + return ActionOutcome::fail( + "Fill", + format!("selector not found: {}", truncate_utf8_tail(sel, 80)), + ); + } + } + } + ActionOutcome::fail("Fill", "missing selector or value") + } + "Type" => { + let text = value.get("value").and_then(|v| v.as_str()); + if let Some(txt) = text { + // Type into the currently focused element + if let Err(e) = eval_with_timeout( + page, + format!("document.activeElement.value += {}", js_escape(txt)), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("Type", e); + } + return ActionOutcome::ok("Type"); + } + ActionOutcome::fail("Type", "missing value") + } + "Clear" => { + if let Some(selector) = value.as_str() { + if let Err(e) = eval_with_timeout( + page, + format!("document.querySelector({}).value = ''", js_escape(selector)), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("Clear", e); + } + return ActionOutcome::ok("Clear"); + } + ActionOutcome::fail("Clear", "missing selector") + } + "Press" => { + if let Some(key) = value.as_str() { + let escaped = js_escape(key); + if let Err(e) = eval_with_timeout(page, format!( + "document.activeElement.dispatchEvent(new KeyboardEvent('keydown', {{key: {k}, bubbles: true}}));\ + document.activeElement.dispatchEvent(new KeyboardEvent('keypress', {{key: {k}, bubbles: true}}));\ + document.activeElement.dispatchEvent(new KeyboardEvent('keyup', {{key: {k}, bubbles: true}}));", + k = escaped + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("Press", e); + } + return ActionOutcome::ok("Press"); + } + ActionOutcome::fail("Press", "missing key") + } + "KeyDown" => { + if let Some(key) = value.as_str() { + if let Err(e) = eval_with_timeout(page, format!( + "document.activeElement.dispatchEvent(new KeyboardEvent('keydown', {{key: {}, bubbles: true}}))", + js_escape(key) + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("KeyDown", e); + } + return ActionOutcome::ok("KeyDown"); + } + ActionOutcome::fail("KeyDown", "missing key") + } + "KeyUp" => { + if let Some(key) = value.as_str() { + if let Err(e) = eval_with_timeout(page, format!( + "document.activeElement.dispatchEvent(new KeyboardEvent('keyup', {{key: {}, bubbles: true}}))", + js_escape(key) + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("KeyUp", e); + } + return ActionOutcome::ok("KeyUp"); + } + ActionOutcome::fail("KeyUp", "missing key") + } + + // === Scroll Actions === + "ScrollX" => { + let pixels = value.as_i64().unwrap_or(0); + if let Err(e) = eval_with_timeout( + page, + format!("window.scrollBy({}, 0)", pixels), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("ScrollX", e); + } + ActionOutcome::ok("ScrollX") + } + "ScrollY" => { + let pixels = value.as_i64().unwrap_or(300); + if let Err(e) = eval_with_timeout( + page, + format!("window.scrollBy(0, {})", pixels), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("ScrollY", e); + } + ActionOutcome::ok("ScrollY") + } + "ScrollTo" => { + if let Some(selector) = value.get("selector").and_then(|v| v.as_str()) { + if let Err(e) = eval_with_timeout(page, format!( + "document.querySelector({})?.scrollIntoView({{behavior: 'smooth', block: 'center'}})", + js_escape(selector) + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("ScrollTo", e); + } + return ActionOutcome::ok("ScrollTo"); + } + ActionOutcome::fail("ScrollTo", "missing selector") + } + "ScrollToPoint" => { + let x = value.get("x").and_then(|v| v.as_i64()).unwrap_or(0); + let y = value.get("y").and_then(|v| v.as_i64()).unwrap_or(0); + if let Err(e) = eval_with_timeout( + page, + format!("window.scrollTo({}, {})", x, y), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("ScrollToPoint", e); + } + ActionOutcome::ok("ScrollToPoint") + } + "InfiniteScroll" => { + let max_scrolls = value + .as_u64() + .unwrap_or(5) + .min(MAX_INFINITE_SCROLL_ITERATIONS); + for i in 0..max_scrolls { + if let Err(e) = eval_with_timeout( + page, + "window.scrollTo(0, document.body.scrollHeight)", + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail( + "InfiniteScroll", + format!("failed at scroll {}: {}", i + 1, e), + ); + } + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + ActionOutcome::ok("InfiniteScroll") + } + + // === Wait Actions === + "Wait" => { + let ms = value.as_u64().unwrap_or(1000).min(MAX_WAIT_FOR_TIMEOUT_MS); + tokio::time::sleep(std::time::Duration::from_millis(ms)).await; + ActionOutcome::ok("Wait") + } + "WaitFor" => { + if let Some(selector) = value.as_str() { + for _ in 0..50 { + if page.find_element(selector).await.is_ok() { + return ActionOutcome::ok("WaitFor"); + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + return ActionOutcome::fail( + "WaitFor", + format!( + "timed out waiting for: {}", + truncate_utf8_tail(selector, 80) + ), + ); + } + ActionOutcome::fail("WaitFor", "missing selector") + } + "WaitForWithTimeout" => { + let selector = value.get("selector").and_then(|v| v.as_str()); + let timeout = value + .get("timeout") + .and_then(|v| v.as_u64()) + .unwrap_or(5000) + .min(MAX_WAIT_FOR_TIMEOUT_MS); + if let Some(sel) = selector { + let iterations = timeout / 100; + for _ in 0..iterations { + if page.find_element(sel).await.is_ok() { + return ActionOutcome::ok("WaitForWithTimeout"); + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + return ActionOutcome::fail( + "WaitForWithTimeout", + format!( + "timed out after {}ms: {}", + timeout, + truncate_utf8_tail(sel, 80) + ), + ); + } + ActionOutcome::fail("WaitForWithTimeout", "missing selector") + } + "WaitForAndClick" => { + if let Some(selector) = value.as_str() { + for _ in 0..50 { + if let Ok(elem) = page.find_element(selector).await { + if elem.click().await.is_ok() { + return ActionOutcome::ok("WaitForAndClick"); + } + return ActionOutcome::fail( + "WaitForAndClick", + "element found but click failed", + ); + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + return ActionOutcome::fail( + "WaitForAndClick", + format!( + "timed out waiting for: {}", + truncate_utf8_tail(selector, 80) + ), + ); + } + ActionOutcome::fail("WaitForAndClick", "missing selector") + } + "WaitForNavigation" => { + // Use the established PageWaitStrategy instead of a fixed sleep + PageWaitStrategy::Load.apply(page).await; + ActionOutcome::ok("WaitForNavigation") + } + "WaitForDom" => { + let selector = value.get("selector").and_then(|v| v.as_str()); + let timeout = value + .get("timeout") + .and_then(|v| v.as_u64()) + .unwrap_or(5000); + tokio::time::sleep(std::time::Duration::from_millis(timeout.min(5000))).await; + if let Some(sel) = selector { + if page.find_element(sel).await.is_ok() { + return ActionOutcome::ok("WaitForDom"); + } + return ActionOutcome::fail( + "WaitForDom", + format!( + "selector not found after {}ms: {}", + timeout.min(5000), + truncate_utf8_tail(sel, 80) + ), + ); + } + ActionOutcome::ok("WaitForDom") + } + + // === Navigation Actions === + "Navigate" => { + if let Some(url) = value.as_str() { + match tokio::time::timeout(std::time::Duration::from_secs(30), page.goto(url)) + .await + { + Ok(Ok(_)) => return ActionOutcome::ok("Navigate"), + Ok(Err(_)) => { + return ActionOutcome::fail( + "Navigate", + format!("navigation failed: {}", truncate_utf8_tail(url, 80)), + ); + } + Err(_) => { + return ActionOutcome::fail( + "Navigate", + format!( + "navigation timed out after 30s: {}", + truncate_utf8_tail(url, 80) + ), + ); + } + } + } + ActionOutcome::fail("Navigate", "missing URL") + } + "GoBack" => { + if let Err(e) = + eval_with_timeout(page, "window.history.back()", EVAL_TIMEOUT_SECS).await + { + return ActionOutcome::fail("GoBack", e); + } + ActionOutcome::ok("GoBack") + } + "GoForward" => { + if let Err(e) = + eval_with_timeout(page, "window.history.forward()", EVAL_TIMEOUT_SECS).await + { + return ActionOutcome::fail("GoForward", e); + } + ActionOutcome::ok("GoForward") + } + "Reload" => { + if let Err(e) = + eval_with_timeout(page, "window.location.reload()", EVAL_TIMEOUT_SECS).await + { + return ActionOutcome::fail("Reload", e); + } + ActionOutcome::ok("Reload") + } + + // === Hover Actions === + "Hover" => { + if let Some(selector) = value.as_str() { + if let Err(e) = eval_with_timeout(page, format!( + "document.querySelector({})?.dispatchEvent(new MouseEvent('mouseover', {{bubbles: true}}))", + js_escape(selector) + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("Hover", e); + } + return ActionOutcome::ok("Hover"); + } + ActionOutcome::fail("Hover", "missing selector") + } + "HoverPoint" => { + let x = value.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = value.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let _ = page.move_mouse_smooth(Point::new(x, y)).await; + ActionOutcome::ok("HoverPoint") + } + + // === Select/Focus Actions === + "Select" => { + let selector = value.get("selector").and_then(|v| v.as_str()); + let opt_value = value.get("value").and_then(|v| v.as_str()); + if let (Some(sel), Some(val)) = (selector, opt_value) { + let escaped_sel = js_escape(sel); + let escaped_val = js_escape(val); + if let Err(e) = eval_with_timeout(page, format!( + "document.querySelector({s}).value = {v}; document.querySelector({s}).dispatchEvent(new Event('change', {{bubbles: true}}))", + s = escaped_sel, v = escaped_val + ), EVAL_TIMEOUT_SECS).await { + return ActionOutcome::fail("Select", e); + } + return ActionOutcome::ok("Select"); + } + ActionOutcome::fail("Select", "missing selector or value") + } + "Focus" => { + if let Some(selector) = value.as_str() { + if let Err(e) = eval_with_timeout( + page, + format!("document.querySelector({})?.focus()", js_escape(selector)), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("Focus", e); + } + return ActionOutcome::ok("Focus"); + } + ActionOutcome::fail("Focus", "missing selector") + } + "Blur" => { + if let Some(selector) = value.as_str() { + if let Err(e) = eval_with_timeout( + page, + format!("document.querySelector({})?.blur()", js_escape(selector)), + EVAL_TIMEOUT_SECS, + ) + .await + { + return ActionOutcome::fail("Blur", e); + } + return ActionOutcome::ok("Blur"); + } + ActionOutcome::fail("Blur", "missing selector") + } + + // === JavaScript === + "Evaluate" => { + if let Some(code) = value.as_str() { + match eval_with_timeout(page, code, EVAL_TIMEOUT_SECS).await { + Ok(()) => return ActionOutcome::ok("Evaluate"), + Err(e) => { + let truncated = truncate_utf8_tail(&e, 150); + log::debug!("Evaluate JS error: {}", truncated); + return ActionOutcome::fail( + "Evaluate", + format!("JS error: {}", truncated), + ); + } + } + } + ActionOutcome::fail("Evaluate", "missing JS code") + } + + // === Screenshot === + "Screenshot" => { + // Screenshot is handled separately - just mark as executed + ActionOutcome::ok("Screenshot") + } + + // === Viewport / Device Metrics === + "SetViewport" => { + if let Some(obj) = value.as_object() { + let width = obj + .get("width") + .and_then(|v| v.as_i64()) + .unwrap_or(1280) + .max(1) + .min(MAX_VIEWPORT_DIM); + let height = obj + .get("height") + .and_then(|v| v.as_i64()) + .unwrap_or(960) + .max(1) + .min(MAX_VIEWPORT_DIM); + let device_scale_factor = obj + .get("device_scale_factor") + .and_then(|v| v.as_f64()) + .unwrap_or(2.0); + let mobile = obj.get("mobile").and_then(|v| v.as_bool()).unwrap_or(false); + + use chromiumoxide::cdp::browser_protocol::emulation::SetDeviceMetricsOverrideParams; + let params = SetDeviceMetricsOverrideParams::new( + width, + height, + device_scale_factor, + mobile, + ); + let _ = page.execute(params).await; + log::debug!( + "SetViewport: {}x{} @ {}x DPR", + width, + height, + device_scale_factor + ); + return ActionOutcome::ok("SetViewport"); + } + ActionOutcome::fail("SetViewport", "missing viewport object") + } + + // === Validation === + "ValidateChain" => { + // Validation step - always succeeds + ActionOutcome::ok("ValidateChain") + } + + _ => { + log::debug!("Unknown action: {}", action); + ActionOutcome::fail(action, "unknown action") + } + } + } + + // ----------------------------------------------------------------- + // Single-action APIs (act, observe) + // ----------------------------------------------------------------- + + /// Execute a single action on the page based on a natural language instruction. + /// + /// # Arguments + /// * `page` - The Chrome page to act on + /// * `instruction` - Natural language instruction (e.g., "Click the login button") + /// + /// # Returns + /// `ActResult` with success status, action description, and optional screenshot. + pub async fn act(&self, page: &Page, instruction: &str) -> EngineResult { + use super::{best_effort_parse_json_object, extract_assistant_content, extract_usage}; + use serde::Serialize; + + #[derive(Serialize)] + struct Message { + role: String, + content: serde_json::Value, + } + + #[derive(Serialize)] + struct Request { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_tokens: Option, + response_format: serde_json::Value, + } + + // Capture page state + let screenshot = self + .screenshot_as_data_url_with_profile( + page, + &CaptureProfile { + full_page: false, + omit_background: true, + ..Default::default() + }, + ) + .await?; + + let html = page + .content() + .await + .map_err(|e| EngineError::Remote(format!("page.content() failed: {e}")))?; + let cleaned_html = clean_html_with_profile(&html, HtmlCleaningProfile::Default); + let truncated_html = truncate_utf8_tail(&cleaned_html, self.cfg.html_max_bytes); + + let system_prompt = r#"You are a browser automation agent. Given a screenshot and HTML, execute the user's instruction by returning a JSON object with a single step. + +Response format: +{ + "action": "click" | "type" | "scroll" | "wait" | "navigate" | "press" | "select", + "selector": "CSS selector if needed", + "text": "text to type if action is type", + "url": "URL if action is navigate", + "key": "key name if action is press", + "value": "value if action is select", + "x": 0, + "y": 300, + "ms": 1000, + "description": "Brief description of what this action does" +} + +Only return the JSON object, no other text."#; + + let user_content = serde_json::json!([ + { + "type": "text", + "text": format!("HTML:\n{}\n\nInstruction: {}", truncated_html, instruction) + }, + { + "type": "image_url", + "image_url": { "url": screenshot } + } + ]); + + let messages = vec![ + Message { + role: "system".to_string(), + content: serde_json::Value::String(system_prompt.to_string()), + }, + Message { + role: "user".to_string(), + content: user_content, + }, + ]; + + let request = Request { + model: self.model_name.clone(), + messages, + temperature: Some(self.cfg.temperature), + max_tokens: Some(self.cfg.max_tokens as u32), + response_format: serde_json::json!({ "type": "json_object" }), + }; + + // Acquire semaphore if configured + let _permit = self.acquire_llm_permit().await; + + static CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(|| { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()) + }); + + let mut req = CLIENT.post(&self.api_url).json(&request); + if let Some(ref key) = self.api_key { + req = req.bearer_auth(key); + } + + let resp = req.send().await?; + let status = resp.status(); + let body: serde_json::Value = resp.json().await?; + + if !status.is_success() { + return Ok(ActResult::failure(format!( + "API error {}: {}", + status, + serde_json::to_string_pretty(&body).unwrap_or_default() + ))); + } + + let content = extract_assistant_content(&body) + .ok_or_else(|| EngineError::MissingField("assistant content"))?; + let usage = extract_usage(&body); + + let step = best_effort_parse_json_object(&content)?; + + // Execute the action + let (steps_executed, _spawn_pages, _outcomes) = self + .execute_steps(page, std::slice::from_ref(&step), &self.cfg) + .await?; + + let action_type = step + .get("action") + .and_then(|v| v.as_str()) + .map(String::from); + let description = step + .get("description") + .and_then(|v| v.as_str()) + .unwrap_or("Action executed") + .to_string(); + + // Take screenshot after action + let after_screenshot = if self.cfg.screenshot { + self.take_final_screenshot(page).await.ok() + } else { + None + }; + + Ok(ActResult { + success: steps_executed > 0, + action_taken: description, + action_type, + screenshot: after_screenshot, + error: None, + usage, + }) + } + + /// Observe the current page state and return structured information. + /// + /// # Arguments + /// * `page` - The Chrome page to observe + /// + /// # Returns + /// `PageObservation` with interactive elements, forms, navigation options, etc. + pub async fn observe(&self, page: &Page) -> EngineResult { + use super::{best_effort_parse_json_object, extract_assistant_content, extract_usage}; + use serde::Serialize; + + #[derive(Serialize)] + struct Message { + role: String, + content: serde_json::Value, + } + + #[derive(Serialize)] + struct Request { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_tokens: Option, + response_format: serde_json::Value, + } + + // Capture page state + let screenshot = self + .screenshot_as_data_url_with_profile( + page, + &CaptureProfile { + full_page: true, + omit_background: true, + ..Default::default() + }, + ) + .await?; + + let url = page.url().await.ok().flatten().unwrap_or_default(); + let title = page.get_title().await.ok().flatten().unwrap_or_default(); + + let html = page + .content() + .await + .map_err(|e| EngineError::Remote(format!("page.content() failed: {e}")))?; + let cleaned_html = clean_html_with_profile(&html, HtmlCleaningProfile::Default); + let truncated_html = truncate_utf8_tail(&cleaned_html, self.cfg.html_max_bytes); + + let system_prompt = r#"You are a page analysis agent. Analyze the screenshot and HTML to describe the page state. + +Return a JSON object: +{ + "description": "Brief description of the page", + "page_type": "login|search|product|list|article|form|dashboard|other", + "interactive_elements": [ + { + "selector": "CSS selector", + "element_type": "button|link|input|select|textarea|checkbox|radio", + "text": "visible text", + "description": "what this element does", + "visible": true, + "enabled": true + } + ], + "forms": [ + { + "selector": "form CSS selector", + "description": "what this form does", + "fields": [ + { "name": "field name", "field_type": "text|email|password|etc", "label": "label text", "required": true } + ] + } + ], + "navigation": [ + { "text": "link text", "url": "href", "selector": "CSS selector" } + ], + "suggested_actions": ["action 1", "action 2"] +} + +Only return the JSON object."#; + + let user_content = serde_json::json!([ + { + "type": "text", + "text": format!("URL: {}\nTitle: {}\n\nHTML:\n{}", url, title, truncated_html) + }, + { + "type": "image_url", + "image_url": { "url": screenshot } + } + ]); + + let messages = vec![ + Message { + role: "system".to_string(), + content: serde_json::Value::String(system_prompt.to_string()), + }, + Message { + role: "user".to_string(), + content: user_content, + }, + ]; + + let request = Request { + model: self.model_name.clone(), + messages, + temperature: Some(self.cfg.temperature), + max_tokens: Some(self.cfg.max_tokens as u32), + response_format: serde_json::json!({ "type": "json_object" }), + }; + + // Acquire semaphore if configured + let _permit = self.acquire_llm_permit().await; + + static CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(|| { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .build() + .unwrap_or_else(|_| reqwest::Client::new()) + }); + + let mut req = CLIENT.post(&self.api_url).json(&request); + if let Some(ref key) = self.api_key { + req = req.bearer_auth(key); + } + + let resp = req.send().await?; + let status = resp.status(); + let body: serde_json::Value = resp.json().await?; + + if !status.is_success() { + return Err(EngineError::RemoteStatus( + status.as_u16(), + format!( + "API error {}: {}", + status, + serde_json::to_string_pretty(&body).unwrap_or_default() + ), + )); + } + + let content = extract_assistant_content(&body) + .ok_or_else(|| EngineError::MissingField("assistant content"))?; + let usage = extract_usage(&body); + + let parsed = best_effort_parse_json_object(&content)?; + + // Build PageObservation from parsed JSON + let mut obs = PageObservation::new(&url) + .with_title(&title) + .with_usage(usage); + + if let Some(desc) = parsed.get("description").and_then(|v| v.as_str()) { + obs = obs.with_description(desc); + } + + if let Some(pt) = parsed.get("page_type").and_then(|v| v.as_str()) { + obs = obs.with_page_type(pt); + } + + // Parse interactive elements + if let Some(elements) = parsed + .get("interactive_elements") + .and_then(|v| v.as_array()) + { + for elem in elements { + if let Ok(ie) = serde_json::from_value(elem.clone()) { + obs.interactive_elements.push(ie); + } + } + } + + // Parse forms + if let Some(forms) = parsed.get("forms").and_then(|v| v.as_array()) { + for form in forms { + if let Ok(fi) = serde_json::from_value(form.clone()) { + obs.forms.push(fi); + } + } + } + + // Parse navigation + if let Some(navs) = parsed.get("navigation").and_then(|v| v.as_array()) { + for nav in navs { + if let Ok(no) = serde_json::from_value(nav.clone()) { + obs.navigation.push(no); + } + } + } + + // Parse suggested actions + if let Some(actions) = parsed.get("suggested_actions").and_then(|v| v.as_array()) { + for action in actions { + if let Some(s) = action.as_str() { + obs.suggested_actions.push(s.to_string()); + } + } + } + + if self.cfg.screenshot { + obs = obs.with_screenshot(screenshot); + } + + Ok(obs) + } +} + +/// Run remote multi-modal automation with a browser page. +/// +/// This is a convenience function that creates an engine from the configs +/// and runs automation on the page. +#[cfg(feature = "chrome")] +pub async fn run_remote_multimodal_with_page( + cfgs: &super::RemoteMultimodalConfigs, + page: &Page, + url: &str, +) -> EngineResult { + let sem = cfgs.get_or_init_semaphore(); + let mut engine = RemoteMultimodalEngine::new( + cfgs.api_url.clone(), + cfgs.model_name.clone(), + cfgs.system_prompt.clone(), + ) + .with_api_key(cfgs.api_key.as_deref()); + + engine.with_system_prompt_extra(cfgs.system_prompt_extra.as_deref()); + engine.with_user_message_extra(cfgs.user_message_extra.as_deref()); + engine.with_remote_multimodal_config(cfgs.cfg.clone()); + engine.with_prompt_url_gate(cfgs.prompt_url_gate.clone()); + engine.with_semaphore(sem); + engine.with_vision_model(cfgs.vision_model.clone()); + engine.with_text_model(cfgs.text_model.clone()); + engine.with_vision_route_mode(cfgs.vision_route_mode); + engine.with_chrome_ai(cfgs.use_chrome_ai); + engine.with_chrome_ai_max_user_chars(cfgs.chrome_ai_max_user_chars); + #[cfg(feature = "skills")] + if let Some(ref registry) = cfgs.skill_registry { + engine.with_skill_registry(Some(registry.clone())); + } + + // Enable session memory for multi-round automation so memory_ops, + // level-attempt tracking, and force-refresh recovery all work. + if cfgs.cfg.max_rounds > 1 { + let mut mem = super::AutomationMemory::new(); + engine.run_with_memory(page, url, Some(&mut mem)).await + } else { + engine.run(page, url).await + } +} + +/// Result from processing a spawned page. +#[cfg(feature = "chrome")] +#[derive(Debug, Clone)] +pub struct SpawnedPageResult { + /// The URL that was opened. + pub url: String, + /// The automation result from the page. + pub result: Result, + /// Total bytes transferred for this page (from network events). + pub bytes_transferred: Option, + /// Map of request IDs to bytes transferred (for detailed network tracking). + pub response_map: Option>, +} + +#[cfg(feature = "chrome")] +impl SpawnedPageResult { + /// Check if the page was processed successfully. + pub fn is_ok(&self) -> bool { + self.result.is_ok() + } + + /// Get the extracted data from this page, if any. + pub fn extracted(&self) -> Option<&serde_json::Value> { + self.result.as_ref().ok().and_then(|r| r.extracted.as_ref()) + } + + /// Get the screenshot from this page, if any (base64 encoded). + pub fn screenshot(&self) -> Option<&str> { + self.result + .as_ref() + .ok() + .and_then(|r| r.screenshot.as_deref()) + } + + /// Get the label/description from this page. + pub fn label(&self) -> Option<&str> { + self.result.as_ref().ok().map(|r| r.label.as_str()) + } + + /// Get the error message if the page failed. + pub fn error(&self) -> Option<&str> { + self.result.as_ref().err().map(|s| s.as_str()) + } + + /// Get any additional spawn_pages from this page (for recursive crawling). + pub fn spawn_pages(&self) -> Option<&[String]> { + self.result.as_ref().ok().map(|r| r.spawn_pages.as_slice()) + } + + /// Get the token usage from this page. + pub fn usage(&self) -> Option<&super::AutomationUsage> { + self.result.as_ref().ok().map(|r| &r.usage) + } +} + +/// Callback type for setting up event tracking on spawned pages. +/// +/// This allows spider to propagate ChromeEventTracker or similar tracking +/// from the main page to spawned pages. +#[cfg(feature = "chrome")] +pub type PageSetupFn = Box< + dyn Fn(&Page) -> std::pin::Pin + Send>> + Send + Sync, +>; + +/// Strategy for waiting on a spawned page before running automation. +/// +/// Controls how the engine waits for page readiness after navigation. +/// This replaces hard-coded `tokio::time::sleep` calls with real +/// browser-level waiting (network idle, DOM stability, etc.). +#[cfg(feature = "chrome")] +#[derive(Debug, Clone, Default)] +pub enum PageWaitStrategy { + /// Wait for `load` event + `document.readyState === "complete"` (default). + /// + /// Uses a JS promise that resolves when the page fires the `load` event. + /// Roughly equivalent to Puppeteer's `waitUntil: "load"`. + #[default] + Load, + /// Wait for zero in-flight network connections. + /// + /// Uses `Page::wait_for_network_idle_with_timeout()` via CDP + /// `Page.lifecycleEvent("networkIdle")`. Best for SPAs and pages with + /// async data fetching. + NetworkIdle { + /// Timeout for the idle wait. Defaults to 15s. + timeout: std::time::Duration, + }, + /// Wait for ≤2 in-flight network connections. + /// + /// Less strict than `NetworkIdle` — useful for pages with long-polling or + /// analytics beacons that never fully go quiet. + NetworkAlmostIdle { + /// Timeout for the almost-idle wait. Defaults to 15s. + timeout: std::time::Duration, + }, + /// Wait for a specific CSS selector to appear in the DOM. + Selector { + /// The CSS selector to wait for. + selector: String, + /// Timeout. Defaults to 10s. + timeout: std::time::Duration, + }, + /// Wait for DOM mutations to stabilize (no changes for 100ms). + DomStable { + /// Timeout. Defaults to 10s. + timeout: std::time::Duration, + }, + /// Fixed delay (escape hatch — prefer the other variants). + Delay(std::time::Duration), + /// No waiting beyond the initial navigation. + None, +} + +#[cfg(feature = "chrome")] +impl PageWaitStrategy { + /// `NetworkIdle` with a 15-second timeout. + pub fn network_idle() -> Self { + Self::NetworkIdle { + timeout: std::time::Duration::from_secs(15), + } + } + + /// `NetworkAlmostIdle` with a 15-second timeout. + pub fn network_almost_idle() -> Self { + Self::NetworkAlmostIdle { + timeout: std::time::Duration::from_secs(15), + } + } + + /// Wait for a CSS selector with a 10-second timeout. + pub fn selector(sel: impl Into) -> Self { + Self::Selector { + selector: sel.into(), + timeout: std::time::Duration::from_secs(10), + } + } + + /// Apply this wait strategy to a page. Returns `Ok(())` on success or + /// timeout (timeouts are logged as warnings, not errors — the page may + /// still be usable). + pub(crate) async fn apply(&self, page: &Page) { + match self { + Self::Load => { + let js = "new Promise(r => { if (document.readyState === 'complete') r(); else window.addEventListener('load', r); })"; + match tokio::time::timeout(std::time::Duration::from_secs(15), page.evaluate(js)) + .await + { + Ok(Ok(_)) => {} + Ok(Err(e)) => log::warn!("PageWaitStrategy::Load JS error: {e}"), + Err(_) => log::warn!("PageWaitStrategy::Load timed out after 15s"), + } + } + Self::NetworkIdle { timeout } => { + if let Err(e) = page.wait_for_network_idle_with_timeout(*timeout).await { + log::warn!("PageWaitStrategy::NetworkIdle error: {e}"); + } + } + Self::NetworkAlmostIdle { timeout } => { + if let Err(e) = page + .wait_for_network_almost_idle_with_timeout(*timeout) + .await + { + log::warn!("PageWaitStrategy::NetworkAlmostIdle error: {e}"); + } + } + Self::Selector { selector, timeout } => { + match tokio::time::timeout(*timeout, page.find_element(selector)).await { + Ok(Ok(_)) => {} + Ok(Err(e)) => { + log::warn!("PageWaitStrategy::Selector({selector}) error: {e}") + } + Err(_) => { + log::warn!( + "PageWaitStrategy::Selector({selector}) timed out after {}ms", + timeout.as_millis() + ) + } + } + } + Self::DomStable { timeout } => { + let timeout_ms = timeout.as_millis() as u32; + let js = format!( + r#"new Promise((resolve) => {{ + let timer; + const observer = new MutationObserver(() => {{ + clearTimeout(timer); + timer = setTimeout(() => {{ observer.disconnect(); resolve(); }}, 100); + }}); + observer.observe(document.body || document.documentElement, {{ childList: true, subtree: true, attributes: true }}); + timer = setTimeout(() => {{ observer.disconnect(); resolve(); }}, 100); + setTimeout(() => {{ observer.disconnect(); resolve(); }}, {timeout_ms}); + }})"# + ); + match tokio::time::timeout( + *timeout + std::time::Duration::from_secs(2), + page.evaluate(js), + ) + .await + { + Ok(Ok(_)) => {} + Ok(Err(e)) => log::warn!("PageWaitStrategy::DomStable error: {e}"), + Err(_) => log::warn!("PageWaitStrategy::DomStable timed out"), + } + } + Self::Delay(d) => { + tokio::time::sleep(*d).await; + } + Self::None => {} + } + } +} + +/// Options for configuring spawned page automation. +/// +/// Configure extraction prompts, screenshots, and event tracking propagation +/// for pages spawned from `OpenPage` actions. +#[cfg(feature = "chrome")] +#[derive(Default)] +pub struct SpawnPageOptions { + /// Custom extraction prompt for each page. + /// If set, enables extraction mode and uses this prompt. + pub extraction_prompt: Option, + /// Whether to capture screenshots from each page. + pub screenshot: bool, + /// Maximum rounds of automation per page. + pub max_rounds: usize, + /// Additional user message to append to each page's automation. + pub user_message_extra: Option, + /// Optional callback to setup event tracking on each spawned page. + /// Use this to propagate network event tracking from the main page. + pub page_setup: Option>, + /// Whether to track bytes transferred via CDP network events. + pub track_bytes: bool, + /// Maximum number of pages processed concurrently. + /// Defaults to [`DEFAULT_SPAWN_CONCURRENCY`]. + pub max_concurrency: usize, + /// How to wait for page readiness after navigation. + /// Defaults to [`PageWaitStrategy::Load`]. + pub page_wait: PageWaitStrategy, +} + +#[cfg(feature = "chrome")] +impl SpawnPageOptions { + /// Create new options with defaults (screenshot enabled, 1 round, + /// concurrency capped at [`DEFAULT_SPAWN_CONCURRENCY`], + /// waits for page `load` event). + pub fn new() -> Self { + Self { + extraction_prompt: None, + screenshot: true, + max_rounds: 1, + user_message_extra: None, + page_setup: None, + track_bytes: false, + max_concurrency: DEFAULT_SPAWN_CONCURRENCY, + page_wait: PageWaitStrategy::Load, + } + } + + /// Enable extraction with a custom prompt. + pub fn with_extraction(mut self, prompt: impl Into) -> Self { + self.extraction_prompt = Some(prompt.into()); + self + } + + /// Enable or disable screenshots. + pub fn with_screenshot(mut self, enabled: bool) -> Self { + self.screenshot = enabled; + self + } + + /// Set maximum automation rounds per page. + pub fn with_max_rounds(mut self, rounds: usize) -> Self { + self.max_rounds = rounds; + self + } + + /// Add extra user instructions for each page. + pub fn with_user_message(mut self, message: impl Into) -> Self { + self.user_message_extra = Some(message.into()); + self + } + + /// Set a page setup callback for event tracking propagation. + /// This callback is called on each spawned page to setup event listeners, + /// network tracking, etc. from the parent page context. + /// + /// # Example + /// ```ignore + /// use std::sync::Arc; + /// + /// let options = SpawnPageOptions::new() + /// .with_page_setup(Arc::new(|page: &Page| { + /// Box::pin(async move { + /// // Setup event tracking on this page + /// // e.g., setup_chrome_events(page, tracker).await; + /// }) + /// })); + /// ``` + pub fn with_page_setup(mut self, setup: std::sync::Arc) -> Self { + self.page_setup = Some(setup); + self + } + + /// Enable bytes tracking via CDP network events. + /// When enabled, each spawned page will track bytes transferred and + /// populate `SpawnedPageResult.bytes_transferred` and `response_map`. + pub fn with_track_bytes(mut self, enabled: bool) -> Self { + self.track_bytes = enabled; + self + } + + /// Set maximum number of pages processed concurrently. + /// A per-call semaphore limits how many spawned tasks do real work + /// at the same time, preventing browser/LLM overload. + pub fn with_max_concurrency(mut self, n: usize) -> Self { + self.max_concurrency = n.max(1); + self + } + + /// Set the page-wait strategy applied after navigation. + /// + /// Determines how the engine waits for page readiness before running + /// automation. Defaults to [`PageWaitStrategy::Load`]. + /// + /// # Example + /// ```ignore + /// let options = SpawnPageOptions::new() + /// .with_page_wait(PageWaitStrategy::network_idle()); + /// ``` + pub fn with_page_wait(mut self, strategy: PageWaitStrategy) -> Self { + self.page_wait = strategy; + self + } +} + +// Manual Debug implementation since PageSetupFn doesn't implement Debug +#[cfg(feature = "chrome")] +impl std::fmt::Debug for SpawnPageOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SpawnPageOptions") + .field("extraction_prompt", &self.extraction_prompt) + .field("screenshot", &self.screenshot) + .field("max_rounds", &self.max_rounds) + .field("user_message_extra", &self.user_message_extra) + .field("page_setup", &self.page_setup.as_ref().map(|_| "")) + .field("track_bytes", &self.track_bytes) + .field("max_concurrency", &self.max_concurrency) + .field("page_wait", &self.page_wait) + .finish() + } +} + +// Manual Clone implementation +#[cfg(feature = "chrome")] +impl Clone for SpawnPageOptions { + fn clone(&self) -> Self { + Self { + extraction_prompt: self.extraction_prompt.clone(), + screenshot: self.screenshot, + max_rounds: self.max_rounds, + user_message_extra: self.user_message_extra.clone(), + page_setup: self.page_setup.clone(), + track_bytes: self.track_bytes, + max_concurrency: self.max_concurrency, + page_wait: self.page_wait.clone(), + } + } +} + +/// Process spawn_pages URLs concurrently with a browser. +/// +/// This function takes URLs from `AutomationResult.spawn_pages` and runs +/// automation on each in a new browser page concurrently. +/// +/// # Arguments +/// * `browser` - The browser to create new pages from +/// * `urls` - URLs to open (typically from `result.spawn_pages`) +/// * `cfgs` - Configuration for the automation engine +/// +/// # Returns +/// A vector of results, one for each URL, in completion order. +/// +/// # Example +/// ```ignore +/// let result = run_remote_multimodal_with_page(&config, &page, url).await?; +/// if result.has_spawn_pages() { +/// let spawn_results = run_spawn_pages_concurrent( +/// &browser, +/// result.spawn_pages, +/// &config, +/// ).await; +/// for spawn_result in spawn_results { +/// println!("{}: {:?}", spawn_result.url, spawn_result.result); +/// } +/// } +/// ``` +#[cfg(feature = "chrome")] +pub async fn run_spawn_pages_concurrent( + browser: &std::sync::Arc, + urls: Vec, + cfgs: &super::RemoteMultimodalConfigs, +) -> Vec { + run_spawn_pages_with_options(browser, urls, cfgs, SpawnPageOptions::new()).await +} + +/// Process spawn_pages URLs concurrently with custom options. +/// +/// This is the full-featured version that allows customizing extraction, +/// screenshots, and other options for each spawned page. +/// +/// # Arguments +/// * `browser` - The browser to create new pages from +/// * `urls` - URLs to open (typically from `result.spawn_pages`) +/// * `base_cfgs` - Base configuration (API URL, model, etc.) +/// * `options` - Options for extraction, screenshots, etc. +/// +/// # Example +/// ```ignore +/// let options = SpawnPageOptions::new() +/// .with_extraction("Extract the main content, title, and any links") +/// .with_screenshot(true) +/// .with_max_rounds(2); +/// +/// let spawn_results = run_spawn_pages_with_options( +/// &browser, +/// result.spawn_pages, +/// &config, +/// options, +/// ).await; +/// +/// for spawn_result in &spawn_results { +/// if let Some(data) = spawn_result.extracted() { +/// println!("Extracted from {}: {}", spawn_result.url, data); +/// } +/// if let Some(screenshot) = spawn_result.screenshot() { +/// // Save screenshot (base64 encoded PNG) +/// println!("Got screenshot from {} ({} bytes)", spawn_result.url, screenshot.len()); +/// } +/// } +/// ``` +#[cfg(feature = "chrome")] +pub async fn run_spawn_pages_with_options( + browser: &std::sync::Arc, + urls: Vec, + base_cfgs: &super::RemoteMultimodalConfigs, + options: SpawnPageOptions, +) -> Vec { + use chromiumoxide::cdp::browser_protocol::network::EventDataReceived; + use dashmap::DashMap; + use futures::StreamExt; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::sync::Arc; + + if urls.is_empty() { + return Vec::new(); + } + + let base_cfgs = Arc::new(base_cfgs.clone()); + let sem = Arc::new(tokio::sync::Semaphore::new(options.max_concurrency.max(1))); + let options = Arc::new(options); + let mut handles = Vec::with_capacity(urls.len()); + + // Spawn all pages concurrently (semaphore limits in-flight work) + for url in urls { + let browser = browser.clone(); + let base_cfgs = base_cfgs.clone(); + let options = options.clone(); + let url_clone = url.clone(); + let sem = sem.clone(); + + handles.push(tokio::spawn(async move { + // Acquire concurrency permit before doing real work + let _permit = match sem.acquire().await { + Ok(p) => p, + Err(_) => { + return SpawnedPageResult { + url: url_clone, + result: Err("Spawn semaphore closed".to_string()), + bytes_transferred: None, + response_map: None, + }; + } + }; + + // Create new page for this URL + let new_page = match browser.new_page(&url_clone).await { + Ok(page) => page, + Err(e) => { + return SpawnedPageResult { + url: url_clone, + result: Err(format!("Failed to create page: {}", e)), + bytes_transferred: None, + response_map: None, + }; + } + }; + + // Run page setup callback if provided (for event tracking propagation) + if let Some(ref setup) = options.page_setup { + setup(&new_page).await; + } + + // Wait for page readiness using the configured strategy + options.page_wait.apply(&new_page).await; + + // Set up bytes tracking if enabled + let total_bytes: Arc = Arc::new(AtomicU64::new(0)); + let response_map: Arc> = Arc::new(DashMap::new()); + + let bytes_listener = if options.track_bytes { + new_page.event_listener::().await.ok() + } else { + None + }; + + // Spawn bytes tracking task if we have a listener + let tracking_handle = if let Some(mut listener) = bytes_listener { + let total_bytes = total_bytes.clone(); + let response_map = response_map.clone(); + Some(tokio::spawn(async move { + while let Some(event) = listener.next().await { + let bytes = event.encoded_data_length as u64; + total_bytes.fetch_add(bytes, Ordering::Relaxed); + // Cap map growth to prevent unbounded memory usage + if response_map.len() < 10_000 { + response_map + .entry(event.request_id.inner().to_string()) + .and_modify(|v| *v += bytes) + .or_insert(bytes); + } + } + })) + } else { + None + }; + + // Build page-specific config from the full base config to preserve + // routing, schemas, relevance gates, and other production knobs. + let mut page_cfgs = (*base_cfgs).clone(); + let mut page_cfg = page_cfgs.cfg.clone(); + page_cfg.max_rounds = options.max_rounds.max(1); + page_cfg.screenshot = options.screenshot; + + // Enable extraction if prompt is provided + if let Some(ref prompt) = options.extraction_prompt { + page_cfg.extra_ai_data = true; + page_cfg.extraction_prompt = Some(prompt.clone()); + } + + page_cfgs.cfg = page_cfg; + + // Add/override user message extra if provided + if let Some(ref msg) = options.user_message_extra { + page_cfgs.user_message_extra = Some(msg.clone()); + } + + // Run automation on the new page + let result = run_remote_multimodal_with_page(&page_cfgs, &new_page, &url_clone) + .await + .map_err(|e| format!("Automation failed: {}", e)); + + // Stop bytes tracking + if let Some(handle) = tracking_handle { + handle.abort(); + } + + // Collect bytes data + let (bytes_transferred, response_map_out) = { + let bytes_val = total_bytes.load(Ordering::Relaxed); + let bytes = if bytes_val > 0 { + Some(bytes_val as f64) + } else { + None + }; + let map = if !response_map.is_empty() { + Some( + response_map + .iter() + .map(|e| (e.key().clone(), *e.value() as f64)) + .collect(), + ) + } else { + None + }; + (bytes, map) + }; + + SpawnedPageResult { + url: url_clone, + result, + bytes_transferred, + response_map: response_map_out, + } + })); + } + + // Collect all results (concurrent execution, wait for all) + let mut results = Vec::with_capacity(handles.len()); + for handle in handles { + match handle.await { + Ok(spawn_result) => results.push(spawn_result), + Err(e) => { + // JoinError - task panicked or was cancelled + results.push(SpawnedPageResult { + url: "unknown".to_string(), + result: Err(format!("Task failed: {}", e)), + bytes_transferred: None, + response_map: None, + }); + } + } + } + + results +} + +/// Page factory type for creating new pages. +/// The factory receives a URL and should return a new Page navigated to that URL. +#[cfg(feature = "chrome")] +pub type PageFactory = Box< + dyn Fn(String) -> std::pin::Pin> + Send>> + + Send + + Sync, +>; + +/// Process spawn_pages URLs concurrently using a page factory function. +/// +/// This version allows spider or other consumers to provide their own method +/// for creating pages, rather than requiring direct Browser access. This is +/// useful when the entry point only has access to a Page reference. +/// +/// # Arguments +/// * `page_factory` - A function that creates new pages from URLs +/// * `urls` - URLs to open (typically from `result.spawn_pages`) +/// * `base_cfgs` - Base configuration (API URL, model, etc.) +/// * `options` - Options for extraction, screenshots, event tracking, etc. +/// +/// # Example +/// ```ignore +/// use std::sync::Arc; +/// +/// // Create a page factory from the browser +/// let browser = Arc::clone(&browser); +/// let page_factory: PageFactory = Box::new(move |url| { +/// let browser = browser.clone(); +/// Box::pin(async move { +/// browser.new_page(&url).await.map_err(|e| e.to_string()) +/// }) +/// }); +/// +/// let options = SpawnPageOptions::new() +/// .with_extraction("Extract page content") +/// .with_page_setup(Arc::new(|page| { +/// Box::pin(async move { +/// // Setup event tracking propagated from main page +/// }) +/// })); +/// +/// let spawn_results = run_spawn_pages_with_factory( +/// Arc::new(page_factory), +/// result.spawn_pages, +/// &config, +/// options, +/// ).await; +/// ``` +#[cfg(feature = "chrome")] +pub async fn run_spawn_pages_with_factory( + page_factory: std::sync::Arc>, + urls: Vec, + base_cfgs: &super::RemoteMultimodalConfigs, + options: SpawnPageOptions, +) -> Vec { + use std::sync::Arc; + + if urls.is_empty() { + return Vec::new(); + } + + let base_cfgs = Arc::new(base_cfgs.clone()); + let sem = Arc::new(tokio::sync::Semaphore::new(options.max_concurrency.max(1))); + let options = Arc::new(options); + let mut handles = Vec::with_capacity(urls.len()); + + // Spawn all pages concurrently (semaphore limits in-flight work) + for url in urls { + let page_factory = page_factory.clone(); + let base_cfgs = base_cfgs.clone(); + let options = options.clone(); + let url_clone = url.clone(); + let sem = sem.clone(); + + handles.push(tokio::spawn(async move { + // Acquire concurrency permit before doing real work + let _permit = match sem.acquire().await { + Ok(p) => p, + Err(_) => { + return SpawnedPageResult { + url: url_clone, + result: Err("Spawn semaphore closed".to_string()), + bytes_transferred: None, + response_map: None, + }; + } + }; + + let result = async { + // Create new page using the factory + let new_page = page_factory(url_clone.clone()) + .await + .map_err(|e| format!("Failed to create page: {}", e))?; + + // Run page setup callback if provided (for event tracking propagation) + if let Some(ref setup) = options.page_setup { + setup(&new_page).await; + } + + // Wait for page readiness using the configured strategy + options.page_wait.apply(&new_page).await; + + // Build page-specific config from the full base config to preserve + // routing, schemas, relevance gates, and other production knobs. + let mut page_cfgs = (*base_cfgs).clone(); + let mut page_cfg = page_cfgs.cfg.clone(); + page_cfg.max_rounds = options.max_rounds.max(1); + page_cfg.screenshot = options.screenshot; + + // Enable extraction if prompt is provided + if let Some(ref prompt) = options.extraction_prompt { + page_cfg.extra_ai_data = true; + page_cfg.extraction_prompt = Some(prompt.clone()); + } + + page_cfgs.cfg = page_cfg; + + // Add/override user message extra if provided + if let Some(ref msg) = options.user_message_extra { + page_cfgs.user_message_extra = Some(msg.clone()); + } + + // Run automation on the new page + run_remote_multimodal_with_page(&page_cfgs, &new_page, &url_clone) + .await + .map_err(|e| format!("Automation failed: {}", e)) + } + .await; + + SpawnedPageResult { + url: url_clone, + result, + bytes_transferred: None, // Set by caller if needed + response_map: None, + } + })); + } + + // Collect all results (concurrent execution, wait for all) + let mut results = Vec::with_capacity(handles.len()); + for handle in handles { + match handle.await { + Ok(spawn_result) => results.push(spawn_result), + Err(e) => { + // JoinError - task panicked or was cancelled + results.push(SpawnedPageResult { + url: "unknown".to_string(), + result: Err(format!("Task failed: {}", e)), + bytes_transferred: None, + response_map: None, + }); + } + } + } + + results +} + +// ============================================================================= +// Tests +// ============================================================================= + +#[cfg(test)] +mod tests { + use super::*; + use crate::automation::DEFAULT_SYSTEM_PROMPT; + + #[test] + fn test_spawn_page_options_default() { + let options = SpawnPageOptions::new(); + assert!(options.extraction_prompt.is_none()); + assert!(options.screenshot); // Default is true + assert_eq!(options.max_rounds, 1); + assert!(options.user_message_extra.is_none()); + assert!(options.page_setup.is_none()); + } + + #[test] + fn test_spawn_page_options_builder() { + let options = SpawnPageOptions::new() + .with_extraction("Extract the title and main content") + .with_screenshot(false) + .with_max_rounds(3) + .with_user_message("Additional instructions"); + + assert_eq!( + options.extraction_prompt, + Some("Extract the title and main content".to_string()) + ); + assert!(!options.screenshot); + assert_eq!(options.max_rounds, 3); + assert_eq!( + options.user_message_extra, + Some("Additional instructions".to_string()) + ); + } + + #[test] + fn test_spawn_page_options_clone() { + let options = SpawnPageOptions::new() + .with_extraction("test") + .with_screenshot(true) + .with_max_rounds(2); + + let cloned = options.clone(); + assert_eq!(cloned.extraction_prompt, options.extraction_prompt); + assert_eq!(cloned.screenshot, options.screenshot); + assert_eq!(cloned.max_rounds, options.max_rounds); + } + + #[test] + fn test_spawn_page_options_debug() { + let options = SpawnPageOptions::new().with_extraction("test"); + let debug_str = format!("{:?}", options); + assert!(debug_str.contains("SpawnPageOptions")); + assert!(debug_str.contains("extraction_prompt")); + } + + #[test] + fn test_spawned_page_result_success() { + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Ok(crate::automation::AutomationResult::success("test", 1) + .with_extracted(serde_json::json!({"title": "Example"})) + .with_screenshot("base64data".to_string())), + bytes_transferred: Some(1024.0), + response_map: None, + }; + + assert!(result.is_ok()); + assert!(result.error().is_none()); + assert_eq!(result.label(), Some("test")); + assert!(result.extracted().is_some()); + assert_eq!(result.screenshot(), Some("base64data")); + assert!(result.usage().is_some()); + assert_eq!(result.bytes_transferred, Some(1024.0)); + } + + #[test] + fn test_spawned_page_result_error() { + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Err("Connection failed".to_string()), + bytes_transferred: None, + response_map: None, + }; + + assert!(!result.is_ok()); + assert_eq!(result.error(), Some("Connection failed")); + assert!(result.label().is_none()); + assert!(result.extracted().is_none()); + assert!(result.screenshot().is_none()); + assert!(result.usage().is_none()); + } + + #[test] + fn test_spawned_page_result_spawn_pages() { + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Ok( + crate::automation::AutomationResult::success("test", 1).with_spawn_pages(vec![ + "https://example.com/page1".to_string(), + "https://example.com/page2".to_string(), + ]), + ), + bytes_transferred: None, + response_map: None, + }; + + let spawn_pages = result.spawn_pages(); + assert!(spawn_pages.is_some()); + assert_eq!(spawn_pages.unwrap().len(), 2); + } + + #[test] + fn test_spawned_page_result_with_bytes_tracking() { + let mut response_map = std::collections::HashMap::new(); + response_map.insert("req1".to_string(), 512.0); + response_map.insert("req2".to_string(), 1024.0); + + let result = SpawnedPageResult { + url: "https://example.com".to_string(), + result: Ok(crate::automation::AutomationResult::success("test", 1)), + bytes_transferred: Some(1536.0), + response_map: Some(response_map), + }; + + assert_eq!(result.bytes_transferred, Some(1536.0)); + assert!(result.response_map.is_some()); + assert_eq!(result.response_map.as_ref().unwrap().len(), 2); + } + + #[test] + fn test_open_page_action_parsing_single_url() { + // Test that OpenPage with a single URL is correctly detected + let step = serde_json::json!({ "OpenPage": "https://example.com" }); + if let Some(obj) = step.as_object() { + for (action, value) in obj { + if action == "OpenPage" { + if let Some(url) = value.as_str() { + assert_eq!(url, "https://example.com"); + } else { + panic!("Expected string URL"); + } + } + } + } + } + + #[test] + fn test_open_page_action_parsing_multiple_urls() { + // Test that OpenPage with multiple URLs is correctly detected + let step = serde_json::json!({ "OpenPage": ["https://a.com", "https://b.com"] }); + if let Some(obj) = step.as_object() { + for (action, value) in obj { + if action == "OpenPage" { + if let Some(urls) = value.as_array() { + assert_eq!(urls.len(), 2); + assert_eq!(urls[0].as_str().unwrap(), "https://a.com"); + assert_eq!(urls[1].as_str().unwrap(), "https://b.com"); + } else { + panic!("Expected array of URLs"); + } + } + } + } + } + + #[test] + fn test_text_only_prompt_flavor_preserves_bindings() { + let mut prompt = DEFAULT_SYSTEM_PROMPT.to_string(); + RemoteMultimodalEngine::apply_text_only_prompt_flavor(&mut prompt, false); + + assert!(prompt.contains("No screenshot is provided this round")); + assert!(!prompt + .contains("- Screenshot of current page state (may be omitted in text-only rounds)")); + // Keep explicit bindings/format rules for accuracy. + assert!(prompt.contains("ClickPoint")); + assert!(prompt.contains("SetViewport")); + assert!(prompt.contains("JSON only")); + } + + #[test] + fn test_should_include_screenshot_for_round_respects_model_capability() { + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-3.5-turbo", None); + let cfg = RemoteMultimodalConfig::default(); + assert!(!engine.should_include_screenshot_for_round(&cfg, true)); + + let vision_engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + assert!(vision_engine.should_include_screenshot_for_round(&cfg, true)); + } + + #[test] + fn test_should_include_screenshot_for_round_honors_override() { + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-3.5-turbo", None); + let cfg_true = RemoteMultimodalConfig { + include_screenshot: Some(true), + ..Default::default() + }; + assert!(engine.should_include_screenshot_for_round(&cfg_true, true)); + + let cfg_false = RemoteMultimodalConfig { + include_screenshot: Some(false), + ..Default::default() + }; + assert!(!engine.should_include_screenshot_for_round(&cfg_false, true)); + } + + #[test] + fn test_summarize_step_blocklist_dedup_and_selector_priority() { + let steps = vec![ + serde_json::json!({"Click":"button.verify"}), + serde_json::json!({"Click":"button.verify"}), + serde_json::json!({"Fill":{"selector":"input[name='email']","value":"a@b.c"}}), + serde_json::json!({"ClickPoint":{"x":120.0,"y":240.0}}), + ]; + + let blocklist = RemoteMultimodalEngine::summarize_step_blocklist(&steps, 10); + assert_eq!(blocklist.len(), 3); + assert!(blocklist + .iter() + .any(|s| s.contains("Click: selector=button.verify"))); + assert!(blocklist + .iter() + .any(|s| s.contains("Fill: selector=input[name='email']"))); + assert!(blocklist + .iter() + .any(|s| s.contains("ClickPoint: point=(120.0, 240.0)"))); + } + + #[test] + fn test_build_user_prompt_includes_loop_blocklist() { + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + let cfg = RemoteMultimodalConfig::default(); + let cap = CaptureProfile::default(); + let blocklist = vec![ + "Click: selector=button.verify".to_string(), + "ClickPoint: point=(100.0, 200.0)".to_string(), + ]; + + let prompt = engine.build_user_prompt( + &cfg, + &cap, + "https://example.com", + "https://example.com", + "Example Title", + "", + 2, + true, + 3, + &blocklist, + None, + None, + ); + + assert!(prompt.contains("LOOP BLOCKLIST")); + assert!(prompt.contains("Click: selector=button.verify")); + assert!(prompt.contains("ClickPoint: point=(100.0, 200.0)")); + } + + #[test] + fn test_extracted_level_key() { + let val = serde_json::json!({ + "current_level": 7, + "level_name": "Word Search" + }); + assert_eq!( + RemoteMultimodalEngine::extracted_level_key(Some(&val)).as_deref(), + Some("L7:word-search") + ); + } + + // ==================================================================== + // ActionOutcome unit tests + // ==================================================================== + + #[test] + fn test_action_outcome_ok() { + let outcome = ActionOutcome::ok("Click"); + assert!(outcome.success); + assert_eq!(outcome.action, "Click"); + assert!(outcome.error.is_none()); + } + + #[test] + fn test_action_outcome_fail() { + let outcome = ActionOutcome::fail("Click", "selector not found: #missing"); + assert!(!outcome.success); + assert_eq!(outcome.action, "Click"); + assert_eq!( + outcome.error.as_deref(), + Some("selector not found: #missing") + ); + } + + #[test] + fn test_action_outcome_feedback_line_ok() { + let outcome = ActionOutcome::ok("Navigate"); + assert_eq!(outcome.to_feedback_line(), "- Navigate → ok"); + } + + #[test] + fn test_action_outcome_feedback_line_fail() { + let outcome = ActionOutcome::fail("Fill", "selector not found: input#name"); + assert_eq!( + outcome.to_feedback_line(), + "- Fill → FAILED: selector not found: input#name" + ); + } + + #[test] + fn test_action_outcome_feedback_line_fail_no_reason() { + let outcome = ActionOutcome { + action: "Evaluate".to_string(), + success: false, + error: None, + }; + assert_eq!( + outcome.to_feedback_line(), + "- Evaluate → FAILED: unknown error" + ); + } + + // ==================================================================== + // format_action_feedback unit tests + // ==================================================================== + + #[test] + fn test_format_action_feedback_empty() { + assert!(format_action_feedback(&[]).is_none()); + } + + #[test] + fn test_format_action_feedback_all_success() { + let outcomes = vec![ + ActionOutcome::ok("Click"), + ActionOutcome::ok("Fill"), + ActionOutcome::ok("Navigate"), + ]; + // No failures → no feedback (avoid noise) + assert!(format_action_feedback(&outcomes).is_none()); + } + + #[test] + fn test_format_action_feedback_with_failure() { + let outcomes = vec![ + ActionOutcome::ok("Click"), + ActionOutcome::fail("Fill", "selector not found: input#name"), + ActionOutcome::ok("Navigate"), + ]; + let feedback = format_action_feedback(&outcomes); + assert!(feedback.is_some()); + let text = feedback.unwrap(); + assert!(text.starts_with("PREVIOUS ACTION RESULTS:\n")); + assert!(text.contains("- Click → ok")); + assert!(text.contains("- Fill → FAILED: selector not found: input#name")); + assert!(text.contains("- Navigate → ok")); + } + + #[test] + fn test_format_action_feedback_multiple_failures() { + let outcomes = vec![ + ActionOutcome::fail("Click", "selector not found: #btn"), + ActionOutcome::fail("Evaluate", "JS error: ReferenceError: x is not defined"), + ]; + let feedback = format_action_feedback(&outcomes).unwrap(); + assert!(feedback.contains("PREVIOUS ACTION RESULTS:")); + assert!(feedback.contains("Click → FAILED")); + assert!(feedback.contains("Evaluate → FAILED: JS error:")); + } + + #[test] + fn test_format_action_feedback_evaluate_js_error() { + let outcomes = vec![ActionOutcome::fail( + "Evaluate", + "JS error: SyntaxError: Unexpected token '}' at line 3", + )]; + let feedback = format_action_feedback(&outcomes).unwrap(); + assert!(feedback.contains("Evaluate → FAILED: JS error: SyntaxError")); + } + + // ==================================================================== + // Partial result preservation test + // ==================================================================== + + #[test] + fn test_automation_result_preserves_extracted_on_failure() { + // Simulates the partial result path: LLM error after some rounds + // succeeded with extracted data. + let extracted = serde_json::json!({"title": "Example", "items": [1, 2, 3]}); + let result = AutomationResult { + label: "test".to_string(), + steps_executed: 5, + success: false, + error: Some("LLM inference failed on round 4: Http timeout".to_string()), + usage: AutomationUsage { + prompt_tokens: 1000, + completion_tokens: 200, + total_tokens: 1200, + llm_calls: 3, + ..Default::default() + }, + extracted: Some(extracted.clone()), + screenshot: None, + spawn_pages: vec!["https://example.com/page1".to_string()], + relevant: Some(true), + reasoning: Some("Processing data".to_string()), + }; + + // Verify all accumulated data is preserved even on failure + assert!(!result.success); + assert!(result.error.is_some()); + assert!(result + .error + .as_ref() + .unwrap() + .contains("LLM inference failed")); + assert_eq!(result.extracted.as_ref().unwrap(), &extracted); + assert_eq!(result.steps_executed, 5); + assert_eq!(result.usage.llm_calls, 3); + assert_eq!(result.spawn_pages.len(), 1); + assert_eq!(result.relevant, Some(true)); + assert!(result.reasoning.is_some()); + } + + #[test] + fn test_automation_result_failure_with_no_prior_data() { + // LLM fails on round 1 — no data accumulated + let result = AutomationResult { + label: "automation".to_string(), + steps_executed: 0, + success: false, + error: Some("LLM inference failed on round 1: Remote 401 Unauthorized".to_string()), + usage: AutomationUsage::default(), + extracted: None, + screenshot: None, + spawn_pages: vec![], + relevant: None, + reasoning: None, + }; + + assert!(!result.success); + assert!(result.extracted.is_none()); + assert_eq!(result.steps_executed, 0); + } + + // ==================================================================== + // Action feedback injection into user prompt + // ==================================================================== + + #[test] + fn test_action_feedback_prepended_to_user_extra() { + // Simulates the logic in run_with_memory that prepends feedback + let last_action_feedback: Option = Some( + "PREVIOUS ACTION RESULTS:\n- Click → ok\n- Fill → FAILED: selector not found\n\n" + .to_string(), + ); + let effective_user_message_extra: Option = + Some("Complete the form and submit".to_string()); + + let combined: Option = match (&last_action_feedback, &effective_user_message_extra) + { + (Some(feedback), Some(extra)) => { + let mut combined = feedback.clone(); + combined.push_str(extra); + Some(combined) + } + (Some(feedback), None) => Some(feedback.clone()), + (None, Some(extra)) => Some(extra.clone()), + (None, None) => None, + }; + + let text = combined.unwrap(); + assert!(text.starts_with("PREVIOUS ACTION RESULTS:")); + assert!(text.contains("Fill → FAILED: selector not found")); + assert!(text.ends_with("Complete the form and submit")); + } + + #[test] + fn test_action_feedback_only_when_no_user_extra() { + let last_action_feedback: Option = + Some("PREVIOUS ACTION RESULTS:\n- Click → FAILED: selector not found\n\n".to_string()); + let effective_user_message_extra: Option = None; + + let combined: Option = match (&last_action_feedback, &effective_user_message_extra) + { + (Some(feedback), Some(extra)) => { + let mut combined = feedback.clone(); + combined.push_str(extra); + Some(combined) + } + (Some(feedback), None) => Some(feedback.clone()), + (None, Some(extra)) => Some(extra.clone()), + (None, None) => None, + }; + + let text = combined.unwrap(); + assert!(text.starts_with("PREVIOUS ACTION RESULTS:")); + assert!(!text.contains("USER INSTRUCTIONS")); + } + + #[test] + fn test_no_feedback_no_extra() { + let last_action_feedback: Option = None; + let effective_user_message_extra: Option = None; + + let combined: Option = match (&last_action_feedback, &effective_user_message_extra) + { + (Some(feedback), Some(extra)) => { + let mut combined = feedback.clone(); + combined.push_str(extra); + Some(combined) + } + (Some(feedback), None) => Some(feedback.clone()), + (None, Some(extra)) => Some(extra.clone()), + (None, None) => None, + }; + + assert!(combined.is_none()); + } + + // ==================================================================== + // Hardening: js_escape — injection prevention + // ==================================================================== + + /// Verifies js_escape produces valid JSON string literals that prevent + /// code injection when interpolated into querySelector() and event constructors. + #[test] + fn test_js_escape_prevents_selector_injection() { + // Attacker supplies a selector that tries to break out of the string + // and execute arbitrary JS: + let malicious = "'); document.cookie='stolen'; ('"; + let escaped = js_escape(malicious); + let js = format!("document.querySelector({}).value = ''", escaped); + + // The result must be a single string arg — no break-out. + // The entire malicious payload is safely inside double quotes. + assert!( + js.starts_with("document.querySelector(\""), + "escaped selector must be double-quoted" + ); + // Critically: the ') at the start of the payload is escaped — + // it cannot close a preceding quote and start new code. + // Verify the payload is enclosed within the JSON string literal + // (the escaped version can be round-tripped back losslessly). + let roundtrip: String = serde_json::from_str(&escaped).unwrap(); + assert_eq!(roundtrip, malicious, "round-trip must be lossless"); + // The entire malicious string is a single querySelector argument, + // not three separate JS statements: + assert_eq!( + js.matches("document.querySelector").count(), + 1, + "must be a single querySelector call, not multiple statements" + ); + } + + #[test] + fn test_js_escape_prevents_value_injection_via_type_action() { + // Attacker tries to close the string and inject alert(1) + let malicious = "\"; alert(1); \""; + let escaped = js_escape(malicious); + let js = format!("document.activeElement.value += {}", escaped); + + // The inner double quotes must be backslash-escaped, not raw + assert!( + js.contains(r#"\""#), + "double quotes in value must be escaped" + ); + // Verify we can round-trip through serde_json (it's valid JSON) + let roundtrip: String = serde_json::from_str(&escaped).unwrap(); + assert_eq!( + roundtrip, malicious, + "round-trip through JSON must be lossless" + ); + } + + #[test] + fn test_js_escape_handles_all_dangerous_chars() { + // Every char that could break a JS string literal + for (input, desc) in [ + ("\\", "backslash"), + ("\"", "double quote"), + ("\n", "newline"), + ("\r", "carriage return"), + ("\t", "tab"), + ("\x00", "null byte"), + ] { + let escaped = js_escape(input); + // Must always be valid JSON + let _: String = serde_json::from_str(&escaped) + .unwrap_or_else(|_| panic!("js_escape({desc}) must produce valid JSON")); + } + } + + // ==================================================================== + // Hardening: LLM-controlled duration caps + // ==================================================================== + + /// Verifies the Wait action caps LLM-supplied durations at 30s. + #[test] + fn test_wait_duration_capped_at_30s() { + // Simulate: value.as_u64().unwrap_or(1000).min(MAX_WAIT_FOR_TIMEOUT_MS) + let llm_wants_5min: u64 = 300_000; + let actual = llm_wants_5min.min(MAX_WAIT_FOR_TIMEOUT_MS); + assert_eq!(actual, 30_000, "Wait must be capped at 30s"); + + // Normal values pass through + assert_eq!(2000u64.min(MAX_WAIT_FOR_TIMEOUT_MS), 2000); + } + + /// Verifies ClickHold/ClickHoldPoint cap hold duration at 30s. + #[test] + fn test_click_hold_duration_capped_at_30s() { + let llm_wants_2min: u64 = 120_000; + let actual = llm_wants_2min.min(MAX_HOLD_MS); + assert_eq!(actual, 30_000, "ClickHold must be capped at 30s"); + + assert_eq!(500u64.min(MAX_HOLD_MS), 500, "normal hold_ms unchanged"); + } + + /// Verifies SetViewport clamps width/height to [1, 7680] (8K). + #[test] + fn test_viewport_dimension_clamped() { + // Huge values capped + assert_eq!(99999i64.max(1).min(MAX_VIEWPORT_DIM), 7680); + // Zero/negative floored to 1 + assert_eq!(0i64.max(1).min(MAX_VIEWPORT_DIM), 1); + assert_eq!((-100i64).max(1).min(MAX_VIEWPORT_DIM), 1); + // Normal value passes through + assert_eq!(1920i64.max(1).min(MAX_VIEWPORT_DIM), 1920); + } + + // ==================================================================== + // Hardening: OpenPage spawn cap + // ==================================================================== + + /// Verifies OpenPage truncates URL arrays to MAX_SPAWN_PAGES_PER_ACTION. + #[test] + fn test_open_page_array_truncated_to_cap() { + let urls: Vec = (0..50) + .map(|i| serde_json::json!(format!("https://example.com/p{}", i))) + .collect(); + + // Exact logic from the handler + let capped = &urls[..urls.len().min(MAX_SPAWN_PAGES_PER_ACTION)]; + assert_eq!(capped.len(), 20, "must truncate to 20"); + // First and last capped elements are correct + assert_eq!(capped[0].as_str().unwrap(), "https://example.com/p0"); + assert_eq!(capped[19].as_str().unwrap(), "https://example.com/p19"); + } + + #[test] + fn test_open_page_array_under_cap_unchanged() { + let urls: Vec = (0..3) + .map(|i| serde_json::json!(format!("https://example.com/p{}", i))) + .collect(); + let capped = &urls[..urls.len().min(MAX_SPAWN_PAGES_PER_ACTION)]; + assert_eq!(capped.len(), 3, "small arrays pass through unchanged"); + } + + // ==================================================================== + // Hardening: Navigate timeout pattern + // ==================================================================== + + /// Verifies the Navigate timeout wrapper produces correct outcomes. + #[tokio::test] + async fn test_navigate_timeout_fires_on_slow_future() { + // Simulate what happens when page.goto hangs: tokio::time::timeout fires + let result = tokio::time::timeout( + std::time::Duration::from_millis(10), + tokio::time::sleep(std::time::Duration::from_secs(60)), + ) + .await; + assert!( + result.is_err(), + "timeout must fire for a hanging navigation" + ); + } + + /// Verifies the Navigate timeout passes through on fast completion. + #[tokio::test] + async fn test_navigate_timeout_passes_on_fast_future() { + let result = tokio::time::timeout(std::time::Duration::from_secs(30), async { + Ok::<_, String>(()) + }) + .await; + assert!(result.is_ok(), "fast completion must not timeout"); + assert!(result.unwrap().is_ok()); + } + + // ==================================================================== + // Hardening: Screenshot timeout pattern + // ==================================================================== + + #[tokio::test] + async fn test_screenshot_timeout_fires_on_hang() { + let result = tokio::time::timeout( + std::time::Duration::from_millis(10), + tokio::time::sleep(std::time::Duration::from_secs(60)), + ) + .await; + assert!(result.is_err(), "screenshot timeout must fire on hang"); + } + + #[tokio::test] + async fn test_screenshot_timeout_passes_on_success() { + let result = tokio::time::timeout(std::time::Duration::from_secs(15), async { + Ok::, String>(vec![0x89, 0x50, 0x4E, 0x47]) + }) + .await; + assert!(result.is_ok()); + let png = result.unwrap().unwrap(); + assert_eq!(png[0], 0x89, "PNG magic byte preserved"); + } + + // ==================================================================== + // Hardening: WaitForNavigation uses PageWaitStrategy + // ==================================================================== + + /// Verifies PageWaitStrategy::Load is the default variant used by + /// WaitForNavigation, ensuring it waits for readyState=complete + /// rather than a fixed sleep. + #[test] + fn test_page_wait_strategy_load_is_default() { + let strategy = PageWaitStrategy::default(); + assert!( + matches!(strategy, PageWaitStrategy::Load), + "default PageWaitStrategy must be Load" + ); + } +} diff --git a/spider_agent/src/automation/cache.rs b/spider_agent/src/automation/cache.rs new file mode 100644 index 000000000..8609c87aa --- /dev/null +++ b/spider_agent/src/automation/cache.rs @@ -0,0 +1,507 @@ +//! Smart caching with automatic space management. +//! +//! Features: +//! - Bounded memory usage with configurable limits +//! - LRU eviction with size-aware cleanup +//! - TTL-based expiration +//! - Automatic cleanup on memory pressure + +use std::collections::HashMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; + +/// Size-aware LRU cache with automatic cleanup. +/// +/// Manages cache size based on both entry count and estimated memory usage. +#[derive(Debug)] +pub struct SmartCache { + entries: Arc>>>, + /// Maximum number of entries. + max_entries: usize, + /// Maximum total size in bytes. + max_size_bytes: usize, + /// Current estimated size. + current_size: Arc, + /// Default TTL for entries. + default_ttl: Duration, + /// Stats tracking. + stats: Arc, +} + +/// Cache entry with metadata. +#[derive(Debug, Clone)] +pub struct CacheEntry { + /// The cached value. + pub value: V, + /// When this entry was created. + pub created_at: Instant, + /// When this entry was last accessed. + pub last_accessed: Instant, + /// Estimated size in bytes. + pub size_bytes: usize, + /// Time-to-live for this entry. + pub ttl: Duration, + /// Access count. + pub access_count: u32, +} + +/// Trait for cache values that can report their size. +pub trait CacheValue: Clone + Send + Sync + 'static { + /// Estimate the size of this value in bytes. + fn estimated_size(&self) -> usize; +} + +// Implement for common types +impl CacheValue for String { + fn estimated_size(&self) -> usize { + self.len() + std::mem::size_of::() + } +} + +impl CacheValue for Vec { + fn estimated_size(&self) -> usize { + self.len() + std::mem::size_of::>() + } +} + +impl CacheValue for serde_json::Value { + fn estimated_size(&self) -> usize { + // Rough estimate based on JSON serialization + serde_json::to_string(self).map(|s| s.len()).unwrap_or(100) + + std::mem::size_of::() + } +} + +/// Cache statistics. +#[derive(Debug, Default)] +pub struct CacheStats { + hits: AtomicUsize, + misses: AtomicUsize, + evictions: AtomicUsize, + expirations: AtomicUsize, +} + +impl CacheStats { + /// Get hit count. + pub fn hits(&self) -> usize { + self.hits.load(Ordering::Relaxed) + } + + /// Get miss count. + pub fn misses(&self) -> usize { + self.misses.load(Ordering::Relaxed) + } + + /// Get eviction count. + pub fn evictions(&self) -> usize { + self.evictions.load(Ordering::Relaxed) + } + + /// Get hit rate. + pub fn hit_rate(&self) -> f64 { + let hits = self.hits() as f64; + let total = hits + self.misses() as f64; + if total > 0.0 { + hits / total + } else { + 0.0 + } + } + + /// Reset stats. + pub fn reset(&self) { + self.hits.store(0, Ordering::Relaxed); + self.misses.store(0, Ordering::Relaxed); + self.evictions.store(0, Ordering::Relaxed); + self.expirations.store(0, Ordering::Relaxed); + } +} + +impl SmartCache { + /// Create a new cache with default settings. + /// + /// Default: 1000 entries, 50MB max size, 5 minute TTL. + pub fn new() -> Self { + Self::with_limits(1000, 50 * 1024 * 1024, Duration::from_secs(300)) + } + + /// Create with custom limits. + pub fn with_limits(max_entries: usize, max_size_bytes: usize, default_ttl: Duration) -> Self { + Self { + entries: Arc::new(RwLock::new(HashMap::with_capacity(max_entries.min(10000)))), + max_entries, + max_size_bytes, + current_size: Arc::new(AtomicUsize::new(0)), + default_ttl, + stats: Arc::new(CacheStats::default()), + } + } + + /// Create a small cache (100 entries, 5MB). + pub fn small() -> Self { + Self::with_limits(100, 5 * 1024 * 1024, Duration::from_secs(60)) + } + + /// Create a large cache (10000 entries, 200MB). + pub fn large() -> Self { + Self::with_limits(10000, 200 * 1024 * 1024, Duration::from_secs(600)) + } + + /// Get a value from the cache. + pub async fn get(&self, key: &str) -> Option { + let now = Instant::now(); + let mut saw_expired = false; + + { + let entries = self.entries.read().await; + if let Some(entry) = entries.get(key) { + if now.duration_since(entry.created_at) <= entry.ttl { + let value = entry.value.clone(); + drop(entries); + + // Best-effort metadata touch. We avoid awaiting a write lock on every hit. + if let Ok(mut entries) = self.entries.try_write() { + if let Some(entry) = entries.get_mut(key) { + if now.duration_since(entry.created_at) <= entry.ttl { + entry.last_accessed = now; + entry.access_count = entry.access_count.saturating_add(1); + } + } + } + + self.stats.hits.fetch_add(1, Ordering::Relaxed); + return Some(value); + } + + saw_expired = true; + } + } + + if saw_expired { + let mut entries = self.entries.write().await; + if let Some(entry) = entries.get(key) { + if now.duration_since(entry.created_at) > entry.ttl { + let size = entry.size_bytes; + entries.remove(key); + self.current_size.fetch_sub(size, Ordering::Relaxed); + self.stats.expirations.fetch_add(1, Ordering::Relaxed); + self.stats.misses.fetch_add(1, Ordering::Relaxed); + return None; + } + + let value = entry.value.clone(); + self.stats.hits.fetch_add(1, Ordering::Relaxed); + return Some(value); + } + } + + self.stats.misses.fetch_add(1, Ordering::Relaxed); + None + } + + /// Set a value in the cache. + pub async fn set(&self, key: impl Into, value: V) { + self.set_with_ttl(key, value, self.default_ttl).await; + } + + /// Set a value with custom TTL. + pub async fn set_with_ttl(&self, key: impl Into, value: V, ttl: Duration) { + let key = key.into(); + let size = value.estimated_size() + key.len() + std::mem::size_of::>(); + + let mut entries = self.entries.write().await; + + // Remove old entry if exists + if let Some(old) = entries.remove(&key) { + self.current_size + .fetch_sub(old.size_bytes, Ordering::Relaxed); + } + + // Enforce entry count and size limits while holding the write lock. + while entries.len() >= self.max_entries { + if !self.evict_lru_locked(&mut entries) { + break; + } + } + + while self.current_size.load(Ordering::Relaxed) + size > self.max_size_bytes { + if !self.evict_lru_locked(&mut entries) { + break; + } + } + + let entry = CacheEntry { + value, + created_at: Instant::now(), + last_accessed: Instant::now(), + size_bytes: size, + ttl, + access_count: 1, + }; + + entries.insert(key, entry); + self.current_size.fetch_add(size, Ordering::Relaxed); + } + + /// Remove a value from the cache. + pub async fn remove(&self, key: &str) -> Option { + let mut entries = self.entries.write().await; + if let Some(entry) = entries.remove(key) { + self.current_size + .fetch_sub(entry.size_bytes, Ordering::Relaxed); + Some(entry.value) + } else { + None + } + } + + /// Clear the entire cache. + pub async fn clear(&self) { + let mut entries = self.entries.write().await; + entries.clear(); + self.current_size.store(0, Ordering::Relaxed); + } + + /// Get current entry count. + pub async fn len(&self) -> usize { + self.entries.read().await.len() + } + + /// Check if cache is empty. + pub async fn is_empty(&self) -> bool { + self.entries.read().await.is_empty() + } + + /// Get current size in bytes. + pub fn size_bytes(&self) -> usize { + self.current_size.load(Ordering::Relaxed) + } + + /// Get cache statistics. + pub fn stats(&self) -> &CacheStats { + &self.stats + } + + fn evict_lru_locked(&self, entries: &mut HashMap>) -> bool { + let Some(lru_key) = entries + .iter() + .min_by_key(|(_, e)| e.last_accessed) + .map(|(k, _)| k.clone()) + else { + return false; + }; + + if let Some(entry) = entries.remove(&lru_key) { + self.current_size + .fetch_sub(entry.size_bytes, Ordering::Relaxed); + self.stats.evictions.fetch_add(1, Ordering::Relaxed); + true + } else { + false + } + } + + /// Clean up expired entries. + pub async fn cleanup_expired(&self) { + let mut entries = self.entries.write().await; + let now = Instant::now(); + + let expired: Vec = entries + .iter() + .filter(|(_, e)| now.duration_since(e.created_at) > e.ttl) + .map(|(k, _)| k.clone()) + .collect(); + + for key in expired { + if let Some(entry) = entries.remove(&key) { + self.current_size + .fetch_sub(entry.size_bytes, Ordering::Relaxed); + self.stats.expirations.fetch_add(1, Ordering::Relaxed); + } + } + } + + /// Start a background cleanup task. + /// + /// Runs cleanup every `interval` duration. + pub fn start_cleanup_task(self: Arc, interval: Duration) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + loop { + tokio::time::sleep(interval).await; + self.cleanup_expired().await; + } + }) + } +} + +impl Default for SmartCache { + fn default() -> Self { + Self::new() + } +} + +/// Specialized cache for HTML content. +pub type HtmlCache = SmartCache; + +/// Specialized cache for JSON responses. +pub type JsonCache = SmartCache; + +/// Create a bounded HTML cache. +pub fn html_cache(max_pages: usize, max_mb: usize) -> HtmlCache { + SmartCache::with_limits(max_pages, max_mb * 1024 * 1024, Duration::from_secs(300)) +} + +/// Create a bounded JSON cache. +pub fn json_cache(max_entries: usize, max_mb: usize) -> JsonCache { + SmartCache::with_limits(max_entries, max_mb * 1024 * 1024, Duration::from_secs(300)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_smart_cache_basic() { + let cache: SmartCache = SmartCache::new(); + + cache.set("key1", "value1".to_string()).await; + assert_eq!(cache.get("key1").await, Some("value1".to_string())); + assert_eq!(cache.get("key2").await, None); + } + + #[tokio::test] + async fn test_smart_cache_eviction() { + let cache: SmartCache = + SmartCache::with_limits(2, 1024 * 1024, Duration::from_secs(60)); + + cache.set("key1", "value1".to_string()).await; + cache.set("key2", "value2".to_string()).await; + cache.set("key3", "value3".to_string()).await; // Should evict key1 + + assert_eq!(cache.len().await, 2); + assert_eq!(cache.get("key3").await, Some("value3".to_string())); + } + + #[tokio::test] + async fn test_smart_cache_size_limit() { + // 1KB limit + let cache: SmartCache = SmartCache::with_limits(100, 1024, Duration::from_secs(60)); + + // Add entries until size limit is hit + for i in 0..50 { + cache.set(format!("key{}", i), "x".repeat(100)).await; + } + + // Should have evicted some entries + assert!(cache.size_bytes() <= 1024 + 200); // Some overhead allowed + } + + #[tokio::test] + async fn test_smart_cache_ttl() { + let cache: SmartCache = + SmartCache::with_limits(100, 1024 * 1024, Duration::from_millis(50)); + + cache.set("key1", "value1".to_string()).await; + + // Should exist immediately + assert!(cache.get("key1").await.is_some()); + + // Wait for expiration + tokio::time::sleep(Duration::from_millis(100)).await; + + // Should be expired + assert!(cache.get("key1").await.is_none()); + } + + #[tokio::test] + async fn test_cache_stats() { + let cache: SmartCache = SmartCache::new(); + + cache.set("key1", "value1".to_string()).await; + + cache.get("key1").await; // Hit + cache.get("key1").await; // Hit + cache.get("key2").await; // Miss + + let stats = cache.stats(); + assert_eq!(stats.hits(), 2); + assert_eq!(stats.misses(), 1); + assert!((stats.hit_rate() - 0.666).abs() < 0.01); + } + + #[tokio::test] + async fn test_smart_cache_concurrent_reads_and_writes() { + let cache: Arc> = Arc::new(SmartCache::with_limits( + 1024, + 16 * 1024 * 1024, + Duration::from_secs(120), + )); + + cache.set("shared", "v0".to_string()).await; + + let mut tasks = tokio::task::JoinSet::new(); + + for reader_idx in 0..64usize { + let cache = cache.clone(); + tasks.spawn(async move { + let mut observed = 0usize; + for _ in 0..250usize { + if cache.get("shared").await.is_some() { + observed += 1; + } + } + (reader_idx, observed) + }); + } + + for writer_idx in 0..8usize { + let cache = cache.clone(); + tasks.spawn(async move { + for round in 0..120usize { + let value = format!("writer-{writer_idx}-round-{round}"); + cache.set("shared", value).await; + } + (writer_idx, 120usize) + }); + } + + while let Some(joined) = tasks.join_next().await { + assert!(joined.is_ok(), "task panicked under concurrency"); + } + + assert!(cache.get("shared").await.is_some()); + assert!(cache.len().await <= 1024); + assert!(cache.stats().hits() > 0); + } + + #[tokio::test] + async fn test_smart_cache_concurrent_eviction_stays_bounded() { + let cache: Arc> = Arc::new(SmartCache::with_limits( + 64, + 64 * 1024, + Duration::from_secs(120), + )); + + let mut tasks = tokio::task::JoinSet::new(); + for worker in 0..24usize { + let cache = cache.clone(); + tasks.spawn(async move { + for n in 0..180usize { + let key = format!("w{worker}-k{n}"); + let value = "x".repeat(256); + cache.set(key, value).await; + } + }); + } + + while let Some(joined) = tasks.join_next().await { + assert!(joined.is_ok(), "worker panicked during eviction stress"); + } + + assert!(cache.len().await <= 64); + // leave headroom for accounting overhead + assert!(cache.size_bytes() <= (64 * 1024) + 4096); + } +} diff --git a/spider_agent/src/automation/concurrent_chain.rs b/spider_agent/src/automation/concurrent_chain.rs new file mode 100644 index 000000000..e0771c35a --- /dev/null +++ b/spider_agent/src/automation/concurrent_chain.rs @@ -0,0 +1,131 @@ +//! Concurrent action chain execution with dependency management. +//! +//! This module provides the [`execute_graph`] function for executing actions +//! in parallel where dependencies allow. Independent actions run concurrently +//! via `tokio::JoinSet`, while dependent actions respect their ordering +//! constraints. +//! +//! Data types ([`DependentStep`], [`DependencyGraph`], [`ConcurrentChainConfig`], +//! [`ConcurrentChainResult`], [`StepResult`]) live in [`spider_agent_types`] +//! and are re-exported at the parent module level. + +use super::{ + ConcurrentChainConfig, ConcurrentChainResult, DependencyGraph, DependentStep, StepResult, +}; +use std::time::Instant; + +/// Execute a dependency graph using a provided executor function. +/// +/// This manages the execution loop for a dependency graph, calling the +/// executor for each step and managing parallelism via `tokio::JoinSet`. +/// +/// # Arguments +/// * `graph` - The dependency graph to execute (mutated as steps complete) +/// * `config` - Execution configuration (max parallelism, failure handling) +/// * `executor` - Async function that executes a single step +/// +/// # Returns +/// A [`ConcurrentChainResult`] summarizing the execution outcome. +pub async fn execute_graph( + graph: &mut DependencyGraph, + config: &ConcurrentChainConfig, + executor: F, +) -> ConcurrentChainResult +where + F: Fn(DependentStep) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send + 'static, +{ + let start = Instant::now(); + let mut result = ConcurrentChainResult::new(); + let mut max_parallel_seen = 0; + + while !graph.is_complete() { + if config.stop_on_failure && graph.has_failed() { + break; + } + + let ready_ids = graph.take_ready_steps(config.max_parallel); + if ready_ids.is_empty() { + // No ready steps but not complete - something is wrong + break; + } + + max_parallel_seen = max_parallel_seen.max(ready_ids.len()); + + if config.enable_parallel && ready_ids.len() > 1 { + // Execute in parallel using JoinSet + let mut join_set = tokio::task::JoinSet::new(); + + for step_id in ready_ids { + if let Some(step) = graph.get_step(&step_id).cloned() { + let exec = executor.clone(); + let id = step_id.clone(); + join_set.spawn(async move { + let step_start = Instant::now(); + let mut step_result = exec(step).await; + step_result.duration_ms = step_start.elapsed().as_millis() as u64; + (id, step_result) + }); + } + } + + // Collect results + while let Some(res) = join_set.join_next().await { + match res { + Ok((id, step_result)) => { + graph.complete(&id, step_result.clone()); + result.record(id, step_result); + } + Err(e) => { + // Task panicked + let err_result = StepResult::failure(format!("Task panic: {}", e)); + result.record("unknown".to_string(), err_result); + } + } + } + } else { + // Execute sequentially + for step_id in ready_ids { + if let Some(step) = graph.get_step(&step_id).cloned() { + let step_start = Instant::now(); + let mut step_result = executor(step).await; + step_result.duration_ms = step_start.elapsed().as_millis() as u64; + graph.complete(&step_id, step_result.clone()); + result.record(step_id, step_result); + + if config.stop_on_failure && graph.has_failed() { + break; + } + } + } + } + } + + result.max_parallel = max_parallel_seen; + result.duration_ms = start.elapsed().as_millis() as u64; + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_execute_graph() { + let steps = vec![ + DependentStep::new("a", serde_json::json!({"Wait": 10})), + DependentStep::new("b", serde_json::json!({"Wait": 10})), + DependentStep::new("c", serde_json::json!({"Wait": 10})).depends_on("a"), + ]; + + let mut graph = DependencyGraph::new(steps).expect("valid graph"); + let config = ConcurrentChainConfig::new().with_max_parallel(2); + + let result = + execute_graph(&mut graph, &config, |_step| async { StepResult::success() }).await; + + assert!(result.success); + assert_eq!(result.steps_executed, 3); + assert!(result.max_parallel >= 1); + } +} diff --git a/spider_agent/src/automation/config.rs b/spider_agent/src/automation/config.rs new file mode 100644 index 000000000..720f20c3b --- /dev/null +++ b/spider_agent/src/automation/config.rs @@ -0,0 +1,1045 @@ +//! Runtime configuration for remote multimodal automation. +//! +//! This module contains [`RemoteMultimodalConfigs`], the high-level configuration +//! bundle that wires together all engine settings, concurrency control, and +//! feature-gated capabilities (skills, S3, Chrome AI). +//! +//! Pure data types (e.g. [`RemoteMultimodalConfig`], [`ModelEndpoint`], +//! [`VisionRouteMode`]) live in the [`spider_agent_types`] crate and are +//! re-exported at the parent module level for backward compatibility. + +use std::sync::{Arc, OnceLock}; + +use super::{ + ExtractionSchema, ModelEndpoint, PromptUrlGate, RemoteMultimodalConfig, VisionRouteMode, +}; + +/// Default value for `chrome_ai_max_user_chars`. +fn default_chrome_ai_max_user_chars() -> usize { + 6000 +} + +/// Top-level configuration bundle for remote multimodal automation. +/// +/// This struct combines all the settings needed to drive the +/// [`RemoteMultimodalEngine`](super::RemoteMultimodalEngine): +/// +/// - **API connection** (`api_url`, `api_key`, `model_name`) +/// - **Prompt configuration** (`system_prompt`, `system_prompt_extra`, `user_message_extra`) +/// - **Runtime configuration** ([`RemoteMultimodalConfig`]) +/// - **URL gating** ([`PromptUrlGate`]) +/// - **Dual-model routing** (`vision_model`, `text_model`, `vision_route_mode`) +/// - **Chrome AI** (`use_chrome_ai`, `chrome_ai_max_user_chars`) +/// - **Skills** (feature-gated `skill_registry`, `s3_skill_source`) +/// - **Concurrency** (`concurrency_limit`, lazy semaphore) +/// - **Relevance tracking** (`relevance_credits`, `url_prefilter_cache`) +/// +/// # Example +/// ```rust,ignore +/// use spider_agent::automation::RemoteMultimodalConfigs; +/// +/// let mm = RemoteMultimodalConfigs::new( +/// "https://openrouter.ai/api/v1/chat/completions", +/// "qwen/qwen-2-vl-72b-instruct", +/// ) +/// .with_api_key("sk-or-...") +/// .with_concurrency_limit(5); +/// ``` +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(default)] +pub struct RemoteMultimodalConfigs { + /// OpenAI-compatible chat completions URL. + pub api_url: String, + /// Optional bearer key for `Authorization: Bearer ...` + #[serde(skip_serializing_if = "Option::is_none")] + pub api_key: Option, + /// Model name/id for the target endpoint. + pub model_name: String, + /// Optional base system prompt (None => engine default). + #[serde(skip_serializing_if = "Option::is_none")] + pub system_prompt: Option, + /// Optional extra system instructions appended at runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub system_prompt_extra: Option, + /// Optional extra user instructions appended at runtime. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_message_extra: Option, + /// Runtime knobs (capture policies, retry, looping, etc.) + pub cfg: RemoteMultimodalConfig, + /// Optional URL gating and per-URL overrides. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_url_gate: Option, + /// Optional concurrency limit for remote inference calls. + #[serde(skip_serializing_if = "Option::is_none")] + pub concurrency_limit: Option, + /// Optional vision model endpoint for dual-model routing. + /// When set alongside `text_model`, the engine routes per-round + /// based on [`VisionRouteMode`]. + #[serde(skip_serializing_if = "Option::is_none")] + pub vision_model: Option, + /// Optional text-only model endpoint for dual-model routing. + #[serde(skip_serializing_if = "Option::is_none")] + pub text_model: Option, + /// Routing mode controlling when vision vs text model is used. + #[serde(default)] + pub vision_route_mode: VisionRouteMode, + /// Optional pool of model endpoints for per-round complexity routing. + /// + /// When 3+ models are provided, the engine automatically routes simple + /// rounds to cheap/fast models and complex rounds to powerful/expensive + /// models — with zero extra LLM calls for the routing decision. + /// + /// Pools with 0-2 models are ignored (existing single/dual routing applies). + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub model_pool: Vec, + /// Use Chrome's built-in LanguageModel API (Gemini Nano) for inference. + /// + /// When `true`, the automation loop evaluates JavaScript on the page via + /// `page.evaluate()` calling `LanguageModel.create()` + `session.prompt()` + /// instead of making HTTP API calls. This enables running the agent + /// without any external API key. + /// + /// When left `false` (default), Chrome AI is still used as a **last-resort + /// fallback** if both `api_url` and `api_key` are empty. + /// + /// Requires Chrome with built-in AI enabled: + /// - `chrome://flags/#optimization-guide-on-device-model` → Enabled + /// - `chrome://flags/#prompt-api-for-gemini-nano` → Enabled + #[serde(default)] + pub use_chrome_ai: bool, + /// Maximum user-prompt characters for Chrome AI inference. + /// + /// Gemini Nano has limited context compared to cloud models. This budget + /// controls the max length of the user message (HTML context, URL, title, + /// task instructions). When the user prompt exceeds this limit, the HTML + /// context section is truncated while preserving task instructions and memory. + /// + /// Default: 6000 chars. Only used when Chrome AI is the active inference path. + #[serde(default = "default_chrome_ai_max_user_chars")] + pub chrome_ai_max_user_chars: usize, + /// Optional skill registry for dynamic context injection. + /// When set, matching skills are automatically injected into the system prompt + /// based on current page state (URL, title, HTML) each round. + #[cfg(feature = "skills")] + #[serde(skip)] + pub skill_registry: Option, + /// S3 source for loading skills at startup. + /// When set, skills are fetched from the S3 bucket and merged with any + /// built-in or manually registered skills. + #[cfg(feature = "skills_s3")] + #[serde(skip_serializing_if = "Option::is_none")] + pub s3_skill_source: Option, + /// Semaphore control for concurrency limiting. + #[serde(skip, default = "RemoteMultimodalConfigs::default_semaphore")] + pub semaphore: OnceLock>, + /// Counter for pages deemed irrelevant — each unit = one budget credit to restore. + #[serde(skip)] + pub relevance_credits: Arc, + /// Cache of URL path → relevant classification to avoid re-classifying. + #[serde(skip)] + pub url_prefilter_cache: Arc>, +} + +impl PartialEq for RemoteMultimodalConfigs { + fn eq(&self, other: &Self) -> bool { + self.api_url == other.api_url + && self.api_key == other.api_key + && self.model_name == other.model_name + && self.system_prompt == other.system_prompt + && self.system_prompt_extra == other.system_prompt_extra + && self.user_message_extra == other.user_message_extra + && self.cfg == other.cfg + && self.prompt_url_gate == other.prompt_url_gate + && self.concurrency_limit == other.concurrency_limit + && self.vision_model == other.vision_model + && self.text_model == other.text_model + && self.vision_route_mode == other.vision_route_mode + && self.model_pool == other.model_pool + && self.use_chrome_ai == other.use_chrome_ai + && self.chrome_ai_max_user_chars == other.chrome_ai_max_user_chars + // NOTE: intentionally ignoring `semaphore` and `skill_registry` + } +} + +impl Eq for RemoteMultimodalConfigs {} + +impl Default for RemoteMultimodalConfigs { + fn default() -> Self { + Self { + api_url: String::new(), + api_key: None, + model_name: String::new(), + system_prompt: None, + system_prompt_extra: None, + user_message_extra: None, + cfg: RemoteMultimodalConfig::default(), + prompt_url_gate: None, + concurrency_limit: None, + vision_model: None, + text_model: None, + vision_route_mode: VisionRouteMode::default(), + model_pool: Vec::new(), + use_chrome_ai: false, + chrome_ai_max_user_chars: default_chrome_ai_max_user_chars(), + #[cfg(feature = "skills")] + skill_registry: Some(super::skills::builtin_web_challenges()), + #[cfg(feature = "skills_s3")] + s3_skill_source: None, + semaphore: Self::default_semaphore(), + relevance_credits: Arc::new(std::sync::atomic::AtomicU32::new(0)), + url_prefilter_cache: Arc::new(dashmap::DashMap::new()), + } + } +} + +impl RemoteMultimodalConfigs { + /// Create a new remote multimodal config bundle. + /// + /// This sets the minimum required fields: + /// - `api_url`: the OpenAI-compatible `/v1/chat/completions` endpoint + /// - `model_name`: the model identifier understood by that endpoint + /// + /// All other fields fall back to [`Default::default`]. + /// + /// # Example + /// ```rust + /// use spider_agent::automation::RemoteMultimodalConfigs; + /// + /// let mm = RemoteMultimodalConfigs::new( + /// "http://localhost:11434/v1/chat/completions", + /// "qwen2.5-vl", + /// ); + /// ``` + pub fn new(api_url: impl Into, model_name: impl Into) -> Self { + Self { + api_url: api_url.into(), + model_name: model_name.into(), + ..Default::default() + } + } + + /// Default semaphore. + fn default_semaphore() -> OnceLock> { + OnceLock::new() + } + + /// Get (and lazily init) the shared semaphore from `concurrency_limit`. + /// This is safe to call concurrently; `OnceLock` handles the race. + pub fn get_or_init_semaphore(&self) -> Option> { + let n = self.concurrency_limit?; + if n == 0 { + return None; + } + Some( + self.semaphore + .get_or_init(|| Arc::new(tokio::sync::Semaphore::new(n))) + .clone(), + ) + } + + /// Attach an optional API key for authenticated endpoints. + /// + /// When set, the engine will send: + /// `Authorization: Bearer ` + pub fn with_api_key(mut self, key: impl Into) -> Self { + self.api_key = Some(key.into()); + self + } + + /// Set the base system prompt for the model. + /// + /// - `Some(prompt)` uses your prompt as the base system prompt. + /// - `None` means the engine should use its built-in default system prompt. + pub fn with_system_prompt(mut self, prompt: impl Into) -> Self { + self.system_prompt = Some(prompt.into()); + self + } + + /// Append additional system-level instructions. + /// + /// This is appended after the base system prompt and before any runtime config summary + /// the engine might embed. + pub fn with_system_prompt_extra(mut self, extra: impl Into) -> Self { + self.system_prompt_extra = Some(extra.into()); + self + } + + /// Append additional user instructions for the task. + /// + /// This is appended to the user message after the captured page context. + pub fn with_user_message_extra(mut self, extra: impl Into) -> Self { + self.user_message_extra = Some(extra.into()); + self + } + + /// Replace the runtime automation configuration. + pub fn with_cfg(mut self, cfg: RemoteMultimodalConfig) -> Self { + self.cfg = cfg; + self + } + + /// Set optional URL gating and per-URL overrides. + pub fn with_prompt_url_gate(mut self, gate: PromptUrlGate) -> Self { + self.prompt_url_gate = Some(gate); + self + } + + /// Set an optional concurrency limit for remote inference calls. + pub fn with_concurrency_limit(mut self, limit: usize) -> Self { + self.concurrency_limit = Some(limit); + self + } + + /// Enable extraction mode to return structured data from pages. + pub fn with_extra_ai_data(mut self, enabled: bool) -> Self { + self.cfg.extra_ai_data = enabled; + self + } + + /// Set a custom extraction prompt. + pub fn with_extraction_prompt(mut self, prompt: impl Into) -> Self { + self.cfg.extraction_prompt = Some(prompt.into()); + self + } + + /// Enable screenshot capture after automation completes. + pub fn with_screenshot(mut self, enabled: bool) -> Self { + self.cfg.screenshot = enabled; + self + } + + /// Set a JSON schema for structured extraction output. + pub fn with_extraction_schema(mut self, schema: ExtractionSchema) -> Self { + self.cfg.extraction_schema = Some(schema); + self + } + + /// Check if the configured model supports vision/multimodal input. + /// + /// Uses the `supports_vision` function to detect based on model name. + pub fn model_supports_vision(&self) -> bool { + super::supports_vision(&self.model_name) + } + + /// Determine whether to include screenshots in LLM requests. + /// + /// This respects the `include_screenshot` config override: + /// - `Some(true)`: Always include screenshots + /// - `Some(false)`: Never include screenshots + /// - `None`: Auto-detect based on model name + pub fn should_include_screenshot(&self) -> bool { + match self.cfg.include_screenshot { + Some(explicit) => explicit, + None => self.model_supports_vision(), + } + } + + /// Filter screenshot based on model capabilities. + /// + /// Returns the screenshot if the model supports vision and screenshots + /// are enabled, otherwise returns `None`. + pub fn filter_screenshot<'a>(&self, screenshot: Option<&'a str>) -> Option<&'a str> { + if self.should_include_screenshot() { + screenshot + } else { + None + } + } + + // ── dual-model routing ────────────────────────────────────────── + + /// Set the vision model endpoint for dual-model routing. + pub fn with_vision_model(mut self, endpoint: ModelEndpoint) -> Self { + self.vision_model = Some(endpoint); + self + } + + /// Set the text model endpoint for dual-model routing. + pub fn with_text_model(mut self, endpoint: ModelEndpoint) -> Self { + self.text_model = Some(endpoint); + self + } + + /// Set the vision routing mode. + pub fn with_vision_route_mode(mut self, mode: VisionRouteMode) -> Self { + self.vision_route_mode = mode; + self + } + + /// Convenience: set both vision and text model endpoints at once. + pub fn with_dual_models(mut self, vision: ModelEndpoint, text: ModelEndpoint) -> Self { + self.vision_model = Some(vision); + self.text_model = Some(text); + self + } + + /// Set a pool of model endpoints for per-round complexity routing. + /// + /// When 3+ models are provided, the engine uses [`auto_policy`] to + /// assign models to cost tiers, then picks cheap models for simple + /// rounds and expensive models for complex rounds. + pub fn with_model_pool(mut self, pool: Vec) -> Self { + self.model_pool = pool; + self + } + + // ── S3 skill source ───────────────────────────────────────────── + + /// Set an S3 source for loading skills at startup. + #[cfg(feature = "skills_s3")] + pub fn with_s3_skill_source(mut self, source: super::skills::S3SkillSource) -> Self { + self.s3_skill_source = Some(source); + self + } + + /// Enable relevance gating with optional custom criteria prompt. + pub fn with_relevance_gate(mut self, prompt: Option) -> Self { + self.cfg.relevance_gate = true; + self.cfg.relevance_prompt = prompt; + self + } + + /// Enable URL-level pre-filtering before HTTP fetch. + /// Requires `relevance_gate` to also be enabled. + pub fn with_url_prefilter(mut self, batch_size: Option) -> Self { + self.cfg.url_prefilter = true; + if let Some(bs) = batch_size { + self.cfg.url_prefilter_batch_size = bs; + } + self + } + + /// Enable Chrome built-in AI (LanguageModel / Gemini Nano) for inference. + /// + /// When enabled, the engine uses `page.evaluate()` to call Chrome's + /// `LanguageModel.create()` + `session.prompt()` instead of HTTP API calls. + /// No API key is required. + /// + /// Even when not explicitly enabled, Chrome AI is used as a last-resort + /// fallback if both `api_url` and `api_key` are empty. + pub fn with_chrome_ai(mut self, enabled: bool) -> Self { + self.use_chrome_ai = enabled; + self + } + + /// Set the maximum user-prompt character budget for Chrome AI inference. + pub fn with_chrome_ai_max_user_chars(mut self, chars: usize) -> Self { + self.chrome_ai_max_user_chars = chars; + self + } + + /// Whether Chrome AI should be used for inference in this configuration. + /// + /// Returns `true` when explicitly enabled OR when no API endpoint is + /// configured (last-resort fallback). + pub fn should_use_chrome_ai(&self) -> bool { + self.use_chrome_ai || (self.api_url.is_empty() && self.api_key.is_none()) + } + + /// Whether dual-model routing is active + /// (at least one of `vision_model` / `text_model` is configured). + pub fn has_dual_model_routing(&self) -> bool { + self.vision_model.is_some() || self.text_model.is_some() + } + + /// Resolve the (api_url, model_name, api_key) triple for the current round. + /// + /// * `use_vision == true` → prefer `vision_model`, fall back to primary. + /// * `use_vision == false` → prefer `text_model`, fall back to primary. + /// + /// Fields left as `None` on the chosen [`ModelEndpoint`] inherit from + /// the parent (`self.api_url` / `self.api_key`). + pub fn resolve_model_for_round(&self, use_vision: bool) -> (&str, &str, Option<&str>) { + let endpoint = if use_vision { + self.vision_model.as_ref() + } else { + self.text_model.as_ref() + }; + + match endpoint { + Some(ep) => { + let url = ep.api_url.as_deref().unwrap_or(&self.api_url); + let key = ep.api_key.as_deref().or(self.api_key.as_deref()); + (url, &ep.model_name, key) + } + None => (&self.api_url, &self.model_name, self.api_key.as_deref()), + } + } + + /// Decide whether to use vision this round, based on the configured + /// [`VisionRouteMode`] and current loop state. + /// + /// `force_vision` is an explicit per-round override (e.g. from `request_vision`). + pub fn should_use_vision_this_round( + &self, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + force_vision: bool, + ) -> bool { + if !self.has_dual_model_routing() { + return true; // no routing → always include screenshot (current behaviour) + } + if force_vision { + return true; + } + match self.vision_route_mode { + VisionRouteMode::AlwaysPrimary => true, + VisionRouteMode::TextFirst => round_idx == 0 || stagnated || action_stuck_rounds >= 3, + VisionRouteMode::VisionFirst => round_idx < 2 || stagnated || action_stuck_rounds >= 3, + VisionRouteMode::AgentDriven => false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_remote_multimodal_configs_new() { + let configs = RemoteMultimodalConfigs::new( + "http://localhost:11434/v1/chat/completions", + "qwen2.5-vl", + ); + + assert_eq!( + configs.api_url, + "http://localhost:11434/v1/chat/completions" + ); + assert_eq!(configs.model_name, "qwen2.5-vl"); + assert!(configs.api_key.is_none()); + assert!(configs.system_prompt.is_none()); + } + + #[test] + fn test_remote_multimodal_configs_builder() { + let configs = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o") + .with_api_key("sk-test") + .with_system_prompt("You are a helpful assistant.") + .with_concurrency_limit(5) + .with_screenshot(true); + + assert_eq!(configs.api_key, Some("sk-test".to_string())); + assert_eq!( + configs.system_prompt, + Some("You are a helpful assistant.".to_string()) + ); + assert_eq!(configs.concurrency_limit, Some(5)); + assert!(configs.cfg.screenshot); + } + + #[test] + fn test_remote_multimodal_configs_vision_detection() { + // Vision model + let cfg = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o"); + assert!(cfg.model_supports_vision()); + assert!(cfg.should_include_screenshot()); + + // Non-vision model + let cfg = RemoteMultimodalConfigs::new( + "https://api.openai.com/v1/chat/completions", + "gpt-3.5-turbo", + ); + assert!(!cfg.model_supports_vision()); + assert!(!cfg.should_include_screenshot()); + + // Explicit override to enable screenshots on non-vision model + let mut cfg = RemoteMultimodalConfigs::new( + "https://api.openai.com/v1/chat/completions", + "gpt-3.5-turbo", + ); + cfg.cfg.include_screenshot = Some(true); + assert!(cfg.should_include_screenshot()); + + // Explicit override to disable screenshots on vision model + let mut cfg = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o"); + cfg.cfg.include_screenshot = Some(false); + assert!(!cfg.should_include_screenshot()); + } + + #[test] + fn test_filter_screenshot() { + let screenshot = "base64data..."; + + // Vision model - screenshot passes through + let cfg = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o"); + assert_eq!(cfg.filter_screenshot(Some(screenshot)), Some(screenshot)); + + // Non-vision model - screenshot filtered out + let cfg = RemoteMultimodalConfigs::new( + "https://api.openai.com/v1/chat/completions", + "gpt-3.5-turbo", + ); + assert_eq!(cfg.filter_screenshot(Some(screenshot)), None); + + // No screenshot provided + let cfg = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o"); + assert_eq!(cfg.filter_screenshot(None), None); + } + + #[test] + fn test_has_dual_model_routing() { + // No routing by default + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o"); + assert!(!cfg.has_dual_model_routing()); + + // Vision only + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_vision_model(ModelEndpoint::new("gpt-4o")); + assert!(cfg.has_dual_model_routing()); + + // Text only + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_text_model(ModelEndpoint::new("gpt-4o-mini")); + assert!(cfg.has_dual_model_routing()); + + // Both + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ); + assert!(cfg.has_dual_model_routing()); + } + + #[test] + fn test_resolve_model_for_round_no_routing() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_api_key("sk-parent"); + + // Without dual routing, always returns primary + let (url, model, key) = cfg.resolve_model_for_round(true); + assert_eq!(url, "https://api.example.com"); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-parent")); + + let (url, model, key) = cfg.resolve_model_for_round(false); + assert_eq!(url, "https://api.example.com"); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-parent")); + } + + #[test] + fn test_resolve_model_for_round_dual() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_api_key("sk-parent") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ); + + // Vision round → vision model, inherits parent URL/key + let (url, model, key) = cfg.resolve_model_for_round(true); + assert_eq!(model, "gpt-4o"); + assert_eq!(url, "https://api.example.com"); + assert_eq!(key, Some("sk-parent")); + + // Text round → text model, inherits parent URL/key + let (url, model, key) = cfg.resolve_model_for_round(false); + assert_eq!(model, "gpt-4o-mini"); + assert_eq!(url, "https://api.example.com"); + assert_eq!(key, Some("sk-parent")); + } + + #[test] + fn test_resolve_model_cross_provider() { + // Vision on OpenAI, text on Groq — different URLs and keys + let cfg = + RemoteMultimodalConfigs::new("https://api.openai.com/v1/chat/completions", "gpt-4o") + .with_api_key("sk-openai") + .with_vision_model(ModelEndpoint::new("gpt-4o")) + .with_text_model( + ModelEndpoint::new("llama-3.3-70b-versatile") + .with_api_url("https://api.groq.com/openai/v1/chat/completions") + .with_api_key("gsk-groq"), + ); + + // Vision → uses OpenAI (inherits parent) + let (url, model, key) = cfg.resolve_model_for_round(true); + assert_eq!(url, "https://api.openai.com/v1/chat/completions"); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-openai")); + + // Text → uses Groq (endpoint overrides) + let (url, model, key) = cfg.resolve_model_for_round(false); + assert_eq!(url, "https://api.groq.com/openai/v1/chat/completions"); + assert_eq!(model, "llama-3.3-70b-versatile"); + assert_eq!(key, Some("gsk-groq")); + } + + #[test] + fn test_vision_route_mode_always_primary() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ) + .with_vision_route_mode(VisionRouteMode::AlwaysPrimary); + + // AlwaysPrimary → always vision (true) regardless of round/state + assert!(cfg.should_use_vision_this_round(0, false, 0, false)); + assert!(cfg.should_use_vision_this_round(5, false, 0, false)); + assert!(cfg.should_use_vision_this_round(10, false, 0, false)); + } + + #[test] + fn test_vision_route_mode_text_first() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + // Round 0 → vision + assert!(cfg.should_use_vision_this_round(0, false, 0, false)); + // Round 1+ (no stagnation) → text + assert!(!cfg.should_use_vision_this_round(1, false, 0, false)); + assert!(!cfg.should_use_vision_this_round(5, false, 0, false)); + // Stagnation → upgrade to vision + assert!(cfg.should_use_vision_this_round(3, true, 0, false)); + // Stuck ≥ 3 → upgrade to vision + assert!(cfg.should_use_vision_this_round(5, false, 3, false)); + // Force vision override + assert!(cfg.should_use_vision_this_round(5, false, 0, true)); + } + + #[test] + fn test_vision_route_mode_vision_first() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ) + .with_vision_route_mode(VisionRouteMode::VisionFirst); + + // Rounds 0-1 → vision + assert!(cfg.should_use_vision_this_round(0, false, 0, false)); + assert!(cfg.should_use_vision_this_round(1, false, 0, false)); + // Round 2+ → text + assert!(!cfg.should_use_vision_this_round(2, false, 0, false)); + assert!(!cfg.should_use_vision_this_round(5, false, 0, false)); + // Stagnation → upgrade to vision + assert!(cfg.should_use_vision_this_round(5, true, 0, false)); + // Stuck ≥ 3 → upgrade to vision + assert!(cfg.should_use_vision_this_round(5, false, 3, false)); + } + + #[test] + fn test_no_dual_routing_always_returns_true() { + // Without dual routing set up, should_use_vision_this_round always returns true + // (backwards compatible: every round gets a screenshot) + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o"); + assert!(!cfg.has_dual_model_routing()); + assert!(cfg.should_use_vision_this_round(0, false, 0, false)); + assert!(cfg.should_use_vision_this_round(5, false, 0, false)); + assert!(cfg.should_use_vision_this_round(99, false, 0, false)); + } + + #[test] + fn test_with_dual_models_builder() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "primary") + .with_dual_models( + ModelEndpoint::new("vision-model"), + ModelEndpoint::new("text-model"), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + assert!(cfg.has_dual_model_routing()); + assert_eq!( + cfg.vision_model.as_ref().unwrap().model_name, + "vision-model" + ); + assert_eq!(cfg.text_model.as_ref().unwrap().model_name, "text-model"); + assert_eq!(cfg.vision_route_mode, VisionRouteMode::TextFirst); + } + + #[test] + fn test_configs_serde_with_dual_models() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_api_key("sk-test") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini") + .with_api_url("https://other.api.com") + .with_api_key("sk-other"), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + let json = serde_json::to_string(&cfg).unwrap(); + let deserialized: RemoteMultimodalConfigs = serde_json::from_str(&json).unwrap(); + + assert_eq!(deserialized.model_name, "gpt-4o"); + assert!(deserialized.has_dual_model_routing()); + assert_eq!( + deserialized.vision_model.as_ref().unwrap().model_name, + "gpt-4o" + ); + assert_eq!( + deserialized.text_model.as_ref().unwrap().model_name, + "gpt-4o-mini" + ); + assert_eq!( + deserialized.text_model.as_ref().unwrap().api_url.as_deref(), + Some("https://other.api.com") + ); + assert_eq!(deserialized.vision_route_mode, VisionRouteMode::TextFirst); + } + + #[cfg(feature = "skills")] + #[test] + fn test_default_configs_auto_load_builtin_skills() { + let cfg = RemoteMultimodalConfigs::default(); + let registry = cfg + .skill_registry + .as_ref() + .expect("default config should auto-load built-in skills"); + assert!( + registry.get("image-grid-selection").is_some(), + "expected image-grid-selection built-in skill" + ); + assert!( + registry.get("tic-tac-toe").is_some(), + "expected tic-tac-toe built-in skill" + ); + } + + #[cfg(feature = "skills")] + #[test] + fn test_new_configs_auto_load_builtin_skills() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "model"); + let registry = cfg + .skill_registry + .as_ref() + .expect("new config should auto-load built-in skills"); + assert!( + registry.get("word-search").is_some(), + "expected word-search built-in skill" + ); + } + + // ── Phase 4: Config integration with router ───────────────────────── + + #[test] + fn test_selector_to_dual_model_config() { + use super::super::router::{ModelRequirements, ModelSelector, SelectionStrategy}; + + let mut selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + + // Pick best vision model + let vision_reqs = ModelRequirements::default().with_vision(); + let vision_pick = selector + .select(&vision_reqs) + .expect("should find a vision model"); + + // Pick cheapest text model + selector.set_strategy(SelectionStrategy::CheapestFirst); + let text_reqs = ModelRequirements::default(); + let text_pick = selector + .select(&text_reqs) + .expect("should find a text model"); + + // Build config with dual models from selector picks + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", &vision_pick.name) + .with_dual_models( + ModelEndpoint::new(&vision_pick.name), + ModelEndpoint::new(&text_pick.name), + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + // Verify resolve_model_for_round returns selector's picks + let (_, model, _) = cfg.resolve_model_for_round(true); + assert_eq!( + model, vision_pick.name, + "vision round should use vision pick" + ); + + let (_, model, _) = cfg.resolve_model_for_round(false); + assert_eq!(model, text_pick.name, "text round should use text pick"); + } + + #[test] + fn test_auto_policy_to_configs_round_trip() { + use super::super::router::auto_policy; + + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + + // Build config using policy's tier assignments + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", &policy.large) + .with_dual_models( + ModelEndpoint::new(&policy.large), + ModelEndpoint::new(&policy.small), + ); + + // Serde round-trip + let json = serde_json::to_string(&cfg).unwrap(); + let deserialized: RemoteMultimodalConfigs = serde_json::from_str(&json).unwrap(); + + // Verify resolution survives round-trip + let (_, vision_model, _) = deserialized.resolve_model_for_round(true); + let (_, text_model, _) = deserialized.resolve_model_for_round(false); + assert_eq!(vision_model, policy.large); + assert_eq!(text_model, policy.small); + } + + #[test] + fn test_vision_routing_with_real_capabilities() { + // Use real model names with known vision capabilities + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), // vision-capable + ModelEndpoint::new("gpt-3.5-turbo"), // text-only + ) + .with_vision_route_mode(VisionRouteMode::TextFirst); + + // Round 0 (TextFirst) → vision + assert!(cfg.should_use_vision_this_round(0, false, 0, false)); + let (_, model, _) = cfg.resolve_model_for_round(true); + assert_eq!(model, "gpt-4o"); + assert!( + llm_models_spider::supports_vision(model), + "vision-round model should support vision" + ); + + // Round 3 (no stagnation) → text + assert!(!cfg.should_use_vision_this_round(3, false, 0, false)); + let (_, model, _) = cfg.resolve_model_for_round(false); + assert_eq!(model, "gpt-3.5-turbo"); + assert!( + !llm_models_spider::supports_vision(model), + "text-round model should NOT support vision" + ); + } + + #[test] + fn test_single_model_config_e2e() { + use super::super::router::auto_policy; + + // User has exactly one model — the most common real-world case + let policy = auto_policy(&["gpt-4o"]); + + // Build config from single-model policy (no dual routing) + let cfg = RemoteMultimodalConfigs::new( + "https://api.openai.com/v1/chat/completions", + &policy.large, + ) + .with_api_key("sk-test"); + + // No dual routing active + assert!(!cfg.has_dual_model_routing()); + + // Both vision and text rounds resolve to the same single model + let (url, model, key) = cfg.resolve_model_for_round(true); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-test")); + + let (url2, model2, key2) = cfg.resolve_model_for_round(false); + assert_eq!(url, url2, "single model: same URL for both modes"); + assert_eq!(model, model2, "single model: same model for both modes"); + assert_eq!(key, key2, "single model: same key for both modes"); + + // Serde round-trip preserves single-model config + let json = serde_json::to_string(&cfg).unwrap(); + let deserialized: RemoteMultimodalConfigs = serde_json::from_str(&json).unwrap(); + assert!(!deserialized.has_dual_model_routing()); + let (_, m, _) = deserialized.resolve_model_for_round(true); + assert_eq!(m, "gpt-4o"); + } + + #[test] + fn test_model_resolution_consistency() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_api_key("sk-test") + .with_dual_models( + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini") + .with_api_url("https://other.api.com") + .with_api_key("sk-other"), + ); + + // Call many times — must always return the same result + for _ in 0..100 { + let (url, model, key) = cfg.resolve_model_for_round(true); + assert_eq!(url, "https://api.example.com"); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-test")); + + let (url, model, key) = cfg.resolve_model_for_round(false); + assert_eq!(url, "https://other.api.com"); + assert_eq!(model, "gpt-4o-mini"); + assert_eq!(key, Some("sk-other")); + } + } + + // ── model_pool tests ────────────────────────────────────────────── + + #[test] + fn test_model_pool_default_empty() { + let cfg = RemoteMultimodalConfigs::default(); + assert!(cfg.model_pool.is_empty()); + } + + #[test] + fn test_model_pool_builder() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_model_pool(vec![ + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ModelEndpoint::new("deepseek-chat") + .with_api_url("https://api.deepseek.com/v1/chat/completions") + .with_api_key("sk-ds"), + ]); + assert_eq!(cfg.model_pool.len(), 3); + assert_eq!(cfg.model_pool[2].model_name, "deepseek-chat"); + assert_eq!( + cfg.model_pool[2].api_url.as_deref(), + Some("https://api.deepseek.com/v1/chat/completions") + ); + } + + #[test] + fn test_model_pool_serde_round_trip() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_model_pool(vec![ + ModelEndpoint::new("gpt-4o"), + ModelEndpoint::new("gpt-4o-mini"), + ModelEndpoint::new("deepseek-chat"), + ]); + + let json = serde_json::to_string(&cfg).unwrap(); + assert!(json.contains("model_pool")); + let deserialized: RemoteMultimodalConfigs = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.model_pool.len(), 3); + assert_eq!(deserialized.model_pool[0].model_name, "gpt-4o"); + assert_eq!(deserialized.model_pool[1].model_name, "gpt-4o-mini"); + assert_eq!(deserialized.model_pool[2].model_name, "deepseek-chat"); + } + + #[test] + fn test_model_pool_empty_omitted_from_json() { + let cfg = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o"); + let json = serde_json::to_string(&cfg).unwrap(); + assert!( + !json.contains("model_pool"), + "empty model_pool should be omitted from JSON" + ); + } + + #[test] + fn test_model_pool_equality() { + let a = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_model_pool(vec![ModelEndpoint::new("gpt-4o")]); + let b = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_model_pool(vec![ModelEndpoint::new("gpt-4o")]); + let c = RemoteMultimodalConfigs::new("https://api.example.com", "gpt-4o") + .with_model_pool(vec![ModelEndpoint::new("gpt-4o-mini")]); + assert_eq!(a, b); + assert_ne!(a, c); + } +} diff --git a/spider_agent/src/automation/engine.rs b/spider_agent/src/automation/engine.rs new file mode 100644 index 000000000..48da413dd --- /dev/null +++ b/spider_agent/src/automation/engine.rs @@ -0,0 +1,2367 @@ +//! Remote multimodal engine for LLM-driven automation. +//! +//! Provides the core engine for making API calls to OpenAI-compatible endpoints +//! and extracting structured data from HTML content. + +use reqwest::Client; +use serde::Serialize; +use std::sync::Arc; +use tokio::sync::Semaphore; + +use super::{ + best_effort_parse_json_object, effective_thinking_payload, extract_assistant_content, + extract_thinking_content, extract_usage, is_anthropic_endpoint, reasoning_payload, + truncate_utf8_tail, AutomationResult, AutomationUsage, ContentAnalysis, EngineError, + EngineResult, ExtractionSchema, PromptUrlGate, RemoteMultimodalConfig, DEFAULT_SYSTEM_PROMPT, + EXTRACTION_ONLY_SYSTEM_PROMPT, +}; + +/// Lazy-initialized HTTP client for automation. +static CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(Client::new); + +/// Remote multimodal engine for LLM-driven web automation. +/// +/// This engine makes API calls to OpenAI-compatible endpoints (like OpenRouter) +/// to extract structured data from HTML content. It supports: +/// - HTML-only extraction (no browser required) +/// - HTML + screenshot extraction (multimodal) +/// - Configurable prompts and extraction schemas +/// - Concurrency limiting via semaphore +/// +/// # Example +/// ```rust,ignore +/// use spider_agent::automation::{RemoteMultimodalEngine, RemoteMultimodalConfig}; +/// +/// let engine = RemoteMultimodalEngine::new( +/// "https://openrouter.ai/api/v1/chat/completions", +/// "qwen/qwen-2-vl-72b-instruct", +/// None, +/// ).with_api_key(Some("your-api-key")); +/// +/// let result = engine.extract_from_html( +/// "

Product

$19.99

", +/// "https://example.com/product", +/// Some("Product Page"), +/// ).await?; +/// +/// println!("Extracted: {:?}", result.extracted); +/// ``` +#[derive(Debug, Clone)] +pub struct RemoteMultimodalEngine { + /// Full OpenAI-compatible chat completions endpoint URL. + pub api_url: String, + /// Optional bearer token for authenticated endpoints. + pub api_key: Option, + /// Model identifier understood by the endpoint. + pub model_name: String, + /// Optional base system prompt for the model. + pub system_prompt: Option, + /// Optional extra system instructions appended at runtime. + pub system_prompt_extra: Option, + /// Optional extra user instructions appended at runtime. + pub user_message_extra: Option, + /// Runtime configuration controlling capture, retry, and model policy. + pub cfg: RemoteMultimodalConfig, + /// Optional URL-based gate controlling whether automation runs for a given URL + /// and allowing per-URL config overrides. + pub prompt_url_gate: Option, + /// Optional semaphore used to limit concurrent in-flight LLM requests. + pub semaphore: Option>, + /// Optional vision model endpoint for dual-model routing. + pub vision_model: Option, + /// Optional text-only model endpoint for dual-model routing. + pub text_model: Option, + /// Routing mode controlling when vision vs text model is used. + pub vision_route_mode: super::VisionRouteMode, + /// Optional skill registry for dynamic context injection. + /// When set, matching skills are automatically injected into the system prompt + /// based on current page state (URL, title, HTML) each round. + #[cfg(feature = "skills")] + pub skill_registry: Option, + /// Optional long-term experience memory for learning from past sessions. + /// When set, the engine recalls relevant past strategies before automation + /// and stores successful outcomes after completion. + #[cfg(feature = "memvid")] + pub experience_memory: + Option>>, + /// Use Chrome's built-in LanguageModel API (Gemini Nano) for inference. + /// + /// When `true`, uses `page.evaluate()` + `LanguageModel.create()` instead of + /// HTTP API calls. Also used as a last-resort fallback when `api_url` is empty. + pub use_chrome_ai: bool, + /// Maximum user-prompt characters for Chrome AI inference. + pub chrome_ai_max_user_chars: usize, + /// Optional model router for per-round complexity-based routing. + /// + /// When set (pool has 3+ models), each round classifies its complexity + /// and routes to the appropriate cost tier (cheap for simple, expensive + /// for complex). `None` delegates to existing `resolve_model_for_round`. + pub model_router: Option, + /// Pool of model endpoints for complexity-based routing. + /// + /// Each entry can have its own API URL and key. The router selects + /// which model to use and this pool resolves the connection details. + pub model_pool: Vec, +} + +impl RemoteMultimodalEngine { + /// Create a new remote multimodal engine. + /// + /// # Arguments + /// * `api_url` - OpenAI-compatible chat completions endpoint URL + /// * `model_name` - Model identifier (e.g., "gpt-4o", "qwen/qwen-2-vl-72b-instruct") + /// * `system_prompt` - Optional custom system prompt (defaults to built-in) + pub fn new>(api_url: S, model_name: S, system_prompt: Option) -> Self { + Self { + api_url: api_url.into(), + api_key: None, + model_name: model_name.into(), + system_prompt, + system_prompt_extra: None, + user_message_extra: None, + cfg: RemoteMultimodalConfig::default(), + prompt_url_gate: None, + semaphore: None, + vision_model: None, + text_model: None, + vision_route_mode: super::VisionRouteMode::default(), + #[cfg(feature = "skills")] + skill_registry: None, + #[cfg(feature = "memvid")] + experience_memory: None, + use_chrome_ai: false, + chrome_ai_max_user_chars: 6000, + model_router: None, + model_pool: Vec::new(), + } + } + + /// Set/clear the API key (Bearer token). + pub fn with_api_key(mut self, key: Option<&str>) -> Self { + self.api_key = key.map(|k| k.to_string()); + self + } + + /// Set the runtime configuration. + pub fn with_config(mut self, cfg: RemoteMultimodalConfig) -> Self { + self.cfg = cfg; + self + } + + /// Set maximum concurrent LLM requests. + pub fn with_max_inflight_requests(&mut self, n: usize) -> &mut Self { + if n > 0 { + self.semaphore = Some(Arc::new(Semaphore::new(n))); + } else { + self.semaphore = None; + } + self + } + + /// Provide a shared semaphore for concurrency control. + pub fn with_semaphore(&mut self, sem: Option>) -> &mut Self { + self.semaphore = sem; + self + } + + /// Set extra system prompt instructions. + pub fn with_system_prompt_extra(&mut self, extra: Option<&str>) -> &mut Self { + self.system_prompt_extra = extra.map(|s| s.to_string()); + self + } + + /// Set extra user message instructions. + pub fn with_user_message_extra(&mut self, extra: Option<&str>) -> &mut Self { + self.user_message_extra = extra.map(|s| s.to_string()); + self + } + + /// Set URL-based gating. + pub fn with_prompt_url_gate(&mut self, gate: Option) -> &mut Self { + self.prompt_url_gate = gate; + self + } + + /// Set a skill registry for dynamic context injection. + /// + /// When set, matching skills are automatically injected into the system prompt + /// each round based on the current page state (URL, title, HTML). + #[cfg(feature = "skills")] + pub fn with_skill_registry( + &mut self, + registry: Option, + ) -> &mut Self { + self.skill_registry = registry; + self + } + + /// Set a long-term experience memory for learning across sessions. + /// + /// When set, the engine will recall past successful strategies before + /// each automation run and store new experiences after successful runs. + #[cfg(feature = "memvid")] + pub fn with_experience_memory( + &mut self, + memory: Option< + std::sync::Arc>, + >, + ) -> &mut Self { + self.experience_memory = memory; + self + } + + /// Enable Chrome built-in AI (LanguageModel / Gemini Nano) for inference. + pub fn with_chrome_ai(&mut self, enabled: bool) -> &mut Self { + self.use_chrome_ai = enabled; + self + } + + /// Set the maximum user-prompt character budget for Chrome AI. + pub fn with_chrome_ai_max_user_chars(&mut self, chars: usize) -> &mut Self { + self.chrome_ai_max_user_chars = chars; + self + } + + /// Whether Chrome AI should be used for inference. + /// + /// Returns `true` when explicitly enabled OR when no API endpoint is + /// configured (last-resort fallback). + pub fn should_use_chrome_ai(&self) -> bool { + self.use_chrome_ai || (self.api_url.is_empty() && self.api_key.is_none()) + } + + /// Set the full runtime configuration. + pub fn with_remote_multimodal_config(&mut self, cfg: RemoteMultimodalConfig) -> &mut Self { + self.cfg = cfg; + self + } + + /// Enable/disable extraction mode. + pub fn with_extra_ai_data(&mut self, enabled: bool) -> &mut Self { + self.cfg.extra_ai_data = enabled; + self + } + + /// Set the extraction prompt. + pub fn with_extraction_prompt(&mut self, prompt: Option<&str>) -> &mut Self { + self.cfg.extraction_prompt = prompt.map(|s| s.to_string()); + self + } + + /// Enable/disable screenshot in results. + pub fn with_screenshot(&mut self, enabled: bool) -> &mut Self { + self.cfg.screenshot = enabled; + self + } + + /// Set extraction schema. + pub fn with_extraction_schema(&mut self, schema: Option) -> &mut Self { + self.cfg.extraction_schema = schema; + self + } + + /// Get current configuration. + pub fn config(&self) -> &RemoteMultimodalConfig { + &self.cfg + } + + /// Get prompt URL gate. + pub fn prompt_url_gate(&self) -> Option<&PromptUrlGate> { + self.prompt_url_gate.as_ref() + } + + /// Clone with a different configuration. + pub fn clone_with_cfg(&self, cfg: RemoteMultimodalConfig) -> Self { + Self { + api_url: self.api_url.clone(), + api_key: self.api_key.clone(), + model_name: self.model_name.clone(), + system_prompt: self.system_prompt.clone(), + system_prompt_extra: self.system_prompt_extra.clone(), + user_message_extra: self.user_message_extra.clone(), + cfg, + prompt_url_gate: self.prompt_url_gate.clone(), + semaphore: self.semaphore.clone(), + vision_model: self.vision_model.clone(), + text_model: self.text_model.clone(), + vision_route_mode: self.vision_route_mode, + #[cfg(feature = "skills")] + skill_registry: self.skill_registry.clone(), + #[cfg(feature = "memvid")] + experience_memory: self.experience_memory.clone(), + use_chrome_ai: self.use_chrome_ai, + chrome_ai_max_user_chars: self.chrome_ai_max_user_chars, + model_router: self.model_router.clone(), + model_pool: self.model_pool.clone(), + } + } + + /// Acquire LLM permit for concurrency control. + pub async fn acquire_llm_permit(&self) -> Option { + match &self.semaphore { + Some(sem) => Some(sem.clone().acquire_owned().await.ok()?), + None => None, + } + } + + /// Analyze HTML content for extraction decisions. + pub fn analyze_content(&self, html: &str) -> ContentAnalysis { + ContentAnalysis::analyze(html) + } + + /// Quick check if screenshot is likely needed for extraction. + pub fn needs_screenshot(&self, html: &str) -> bool { + ContentAnalysis::quick_needs_screenshot(html) + } + + /// Resolve per-URL runtime settings from prompt URL gate. + /// + /// Returns `None` when the URL is blocked by the gate. + #[allow(clippy::type_complexity)] + fn resolve_runtime_for_url( + &self, + url: &str, + ) -> Option<( + RemoteMultimodalConfig, + Option, + Option, + Option, + )> { + let mut effective_cfg = self.cfg.clone(); + let mut effective_system_prompt = self.system_prompt.clone(); + let mut effective_system_prompt_extra = self.system_prompt_extra.clone(); + let mut effective_user_message_extra = self.user_message_extra.clone(); + + if let Some(gate) = &self.prompt_url_gate { + let gate_match = gate.match_url(url)?; + if let Some(override_cfg) = gate_match { + let defaults = super::AutomationConfig::default(); + + if override_cfg.max_steps != defaults.max_steps { + effective_cfg.max_rounds = override_cfg.max_steps.max(1); + } + if override_cfg.max_retries != defaults.max_retries { + effective_cfg.retry.max_attempts = override_cfg.max_retries.max(1); + } + if override_cfg.capture_screenshots != defaults.capture_screenshots { + effective_cfg.screenshot = override_cfg.capture_screenshots; + } + if override_cfg.capture_profile != defaults.capture_profile { + effective_cfg.capture_profiles = vec![override_cfg.capture_profile.clone()]; + } + if override_cfg.extract_on_success || override_cfg.extraction_prompt.is_some() { + effective_cfg.extra_ai_data = true; + } + if let Some(extraction_prompt) = &override_cfg.extraction_prompt { + if !extraction_prompt.trim().is_empty() { + effective_cfg.extraction_prompt = Some(extraction_prompt.clone()); + } + } + + if let Some(system_prompt) = &override_cfg.system_prompt { + if !system_prompt.trim().is_empty() { + effective_system_prompt = Some(system_prompt.clone()); + } + } + if let Some(system_prompt_extra) = &override_cfg.system_prompt_extra { + if !system_prompt_extra.trim().is_empty() { + effective_system_prompt_extra = Some(system_prompt_extra.clone()); + } + } + if let Some(user_message_extra) = &override_cfg.user_message_extra { + if !user_message_extra.trim().is_empty() { + effective_user_message_extra = Some(user_message_extra.clone()); + } + } + } + } + + Some(( + effective_cfg, + effective_system_prompt, + effective_system_prompt_extra, + effective_user_message_extra, + )) + } + + /// Compile the system prompt with configuration. + /// Uses `EXTRACTION_ONLY_SYSTEM_PROMPT` for single-round extraction mode, + /// otherwise `DEFAULT_SYSTEM_PROMPT` is always the base. + pub fn system_prompt_compiled(&self, effective_cfg: &RemoteMultimodalConfig) -> String { + let mut s = if effective_cfg.is_extraction_only() { + EXTRACTION_ONLY_SYSTEM_PROMPT.to_string() + } else { + DEFAULT_SYSTEM_PROMPT.to_string() + }; + + // Always keep the default system prompt first, then append configured guidance. + if let Some(base) = &self.system_prompt { + if !base.trim().is_empty() { + s.push_str("\n\n---\nCONFIGURED SYSTEM INSTRUCTIONS:\n"); + s.push_str(base.trim()); + } + } + + // Add any extra system prompt content (but never replace the default) + if let Some(extra) = &self.system_prompt_extra { + if !extra.trim().is_empty() { + s.push_str("\n\n---\nADDITIONAL INSTRUCTIONS:\n"); + s.push_str(extra.trim()); + } + } + + // Add extraction instructions when extra_ai_data is enabled + if effective_cfg.extra_ai_data { + s.push_str("\n\n---\nEXTRACTION MODE ENABLED:\n"); + s.push_str("Include an \"extracted\" field in your JSON response containing structured data extracted from the page.\n"); + + // Add schema instructions if provided + if let Some(schema) = &effective_cfg.extraction_schema { + s.push_str("\nExtraction Schema: "); + s.push_str(&schema.name); + s.push('\n'); + if let Some(desc) = &schema.description { + s.push_str("Description: "); + s.push_str(desc.trim()); + s.push('\n'); + } + s.push_str("\nThe \"extracted\" field MUST conform to this JSON Schema:\n"); + s.push_str(&schema.schema); + s.push('\n'); + if schema.strict { + s.push_str("\nSTRICT MODE: You MUST follow the schema exactly. Do not add extra fields or omit required fields.\n"); + } + } else { + s.push_str("The \"extracted\" field should be a JSON object or array with the relevant data.\n"); + } + + if let Some(extraction_prompt) = &effective_cfg.extraction_prompt { + s.push_str("\nExtraction instructions: "); + s.push_str(extraction_prompt.trim()); + s.push('\n'); + } + + s.push_str("\nExample response with extraction:\n"); + s.push_str("{\n \"label\": \"extract_products\",\n \"done\": true,\n \"steps\": [],\n \"extracted\": {\"products\": [{\"name\": \"Product A\", \"price\": 19.99}]}\n}\n"); + } + + // Add relevance gate instructions + if effective_cfg.relevance_gate { + s.push_str("\n\n---\nRELEVANCE GATE ENABLED:\n"); + s.push_str("Include a \"relevant\": true|false field in your JSON response.\n"); + s.push_str("Set true if the page content is relevant to the extraction/crawl goals.\n"); + s.push_str( + "Set false if the page is off-topic, a 404, login wall, or otherwise not useful.\n", + ); + if let Some(prompt) = &effective_cfg.relevance_prompt { + s.push_str("\nRelevance criteria: "); + s.push_str(prompt.trim()); + s.push('\n'); + } else if let Some(ep) = &effective_cfg.extraction_prompt { + s.push_str("\nJudge relevance against: "); + s.push_str(ep.trim()); + s.push('\n'); + } + } + + s.push_str("\n\n---\nRUNTIME CONFIG (read-only):\n"); + s.push_str(&format!( + "- include_url: {}\n- include_title: {}\n- include_html: {}\n- html_max_bytes: {}\n- temperature: {}\n- max_tokens: {}\n- request_json_object: {}\n- best_effort_json_extract: {}\n- max_rounds: {}\n- extra_ai_data: {}\n- relevance_gate: {}\n", + effective_cfg.include_url, + effective_cfg.include_title, + effective_cfg.include_html, + effective_cfg.html_max_bytes, + effective_cfg.temperature, + effective_cfg.max_tokens, + effective_cfg.request_json_object, + effective_cfg.best_effort_json_extract, + effective_cfg.max_rounds, + effective_cfg.extra_ai_data, + effective_cfg.relevance_gate, + )); + + s + } + + // ── dual-model routing ────────────────────────────────────────── + + /// Set the vision model endpoint for dual-model routing. + pub fn with_vision_model(&mut self, endpoint: Option) -> &mut Self { + self.vision_model = endpoint; + self + } + + /// Set the text model endpoint for dual-model routing. + pub fn with_text_model(&mut self, endpoint: Option) -> &mut Self { + self.text_model = endpoint; + self + } + + /// Set the vision routing mode. + pub fn with_vision_route_mode(&mut self, mode: super::VisionRouteMode) -> &mut Self { + self.vision_route_mode = mode; + self + } + + /// Whether dual-model routing is active. + pub fn has_dual_model_routing(&self) -> bool { + self.vision_model.is_some() || self.text_model.is_some() + } + + /// Resolve (api_url, model_name, api_key) for the current round. + /// + /// Delegates to the same logic as [`RemoteMultimodalConfigs::resolve_model_for_round`] + /// but uses the engine's own fields. + pub fn resolve_model_for_round(&self, use_vision: bool) -> (&str, &str, Option<&str>) { + let endpoint = if use_vision { + self.vision_model.as_ref() + } else { + self.text_model.as_ref() + }; + + match endpoint { + Some(ep) => { + let url = ep.api_url.as_deref().unwrap_or(&self.api_url); + let key = ep.api_key.as_deref().or(self.api_key.as_deref()); + (url, &ep.model_name, key) + } + None => (&self.api_url, &self.model_name, self.api_key.as_deref()), + } + } + + /// Resolve (api_url, model_name, api_key) using complexity-based pool routing. + /// + /// When `model_router` is set (3+ models in pool), classifies the round's + /// complexity and routes to the appropriate cost tier. Falls back to the + /// existing `resolve_model_for_round` when no pool routing is active. + /// + /// If the routed model doesn't support vision but `use_vision` is true, + /// walks up cost tiers to find a vision-capable model in the pool. + pub fn resolve_model_for_round_with_complexity( + &self, + use_vision: bool, + user_prompt: &str, + html_len: usize, + round_idx: usize, + stagnated: bool, + ) -> (&str, &str, Option<&str>) { + let router = match &self.model_router { + Some(r) => r, + None => return self.resolve_model_for_round(use_vision), + }; + + // Classify complexity and route to a tier + let analysis = + super::router::classify_round_complexity(user_prompt, html_len, round_idx, stagnated); + let decision = router.route(&analysis); + let chosen_model = &decision.model; + + // Try to find the chosen model in the pool + if let Some(ep) = self + .model_pool + .iter() + .find(|ep| ep.model_name == *chosen_model) + { + // If vision is needed but this model doesn't support it, find a fallback + if use_vision && !super::supports_vision(&ep.model_name) { + if let Some(fallback) = self.find_vision_fallback_in_pool(&decision.tier) { + let url = fallback.api_url.as_deref().unwrap_or(&self.api_url); + let key = fallback.api_key.as_deref().or(self.api_key.as_deref()); + return (url, &fallback.model_name, key); + } + } + let url = ep.api_url.as_deref().unwrap_or(&self.api_url); + let key = ep.api_key.as_deref().or(self.api_key.as_deref()); + return (url, &ep.model_name, key); + } + + // Model name from router not found in pool — fall back to primary + self.resolve_model_for_round(use_vision) + } + + /// Walk up cost tiers to find a vision-capable model in the pool. + fn find_vision_fallback_in_pool( + &self, + starting_tier: &super::CostTier, + ) -> Option<&super::ModelEndpoint> { + let router = self.model_router.as_ref()?; + let policy = router.policy(); + + // Walk tiers from current up to High, looking for a vision-capable model + let tiers_to_try: &[super::CostTier] = match starting_tier { + super::CostTier::Low => &[super::CostTier::Medium, super::CostTier::High], + super::CostTier::Medium => &[super::CostTier::High], + super::CostTier::High => &[], + }; + + for &tier in tiers_to_try { + let model_name = policy.model_for_tier(tier); + if let Some(ep) = self + .model_pool + .iter() + .find(|ep| ep.model_name == model_name) + { + if super::supports_vision(&ep.model_name) { + return Some(ep); + } + } + } + + // Last resort: any vision-capable model in the pool + self.model_pool + .iter() + .find(|ep| super::supports_vision(&ep.model_name)) + } + + /// Pick a fallback model from the pool, excluding already-tried models. + /// + /// Used by `infer_plan_with_retry` when a retryable error (502, 503, 429, + /// timeout) occurs and the pool has alternative endpoints to try. + /// Returns `(api_url, model_name, api_key)` or `None` if all pool models + /// have been tried. + pub fn pick_fallback_model( + &self, + tried: &[String], + use_vision: bool, + ) -> Option<(String, String, Option)> { + // Prefer vision-capable models when vision is needed, but accept any untried model + let mut candidates: Vec<_> = self + .model_pool + .iter() + .filter(|ep| !tried.iter().any(|t| t == &ep.model_name)) + .collect(); + + if candidates.is_empty() { + return None; + } + + // Sort vision-capable models first when vision is needed + if use_vision { + candidates.sort_by_key(|ep| { + if super::supports_vision(&ep.model_name) { + 0 + } else { + 1 + } + }); + } + + let ep = candidates.first()?; + let url = ep.api_url.as_deref().unwrap_or(&self.api_url).to_string(); + let key = ep + .api_key + .as_deref() + .or(self.api_key.as_deref()) + .map(|s| s.to_string()); + Some((url, ep.model_name.clone(), key)) + } + + /// Decide whether to use vision this round. + pub fn should_use_vision_this_round( + &self, + round_idx: usize, + stagnated: bool, + action_stuck_rounds: usize, + force_vision: bool, + ) -> bool { + if !self.has_dual_model_routing() { + return true; + } + if force_vision { + return true; + } + match self.vision_route_mode { + super::VisionRouteMode::AlwaysPrimary => true, + super::VisionRouteMode::TextFirst => { + round_idx == 0 || stagnated || action_stuck_rounds >= 3 + } + super::VisionRouteMode::VisionFirst => { + round_idx < 2 || stagnated || action_stuck_rounds >= 3 + } + super::VisionRouteMode::AgentDriven => false, + } + } + + /// Extract structured data from raw HTML content (no browser required). + /// + /// This method enables extraction from HTTP responses without Chrome. + /// It sends the HTML to the multimodal model and returns extracted data. + /// + /// # Arguments + /// * `html` - The raw HTML content to extract from + /// * `url` - The URL of the page (for context) + /// * `title` - Optional page title + /// + /// # Returns + /// An `AutomationResult` containing the extracted data in the `extracted` field. + pub async fn extract_from_html( + &self, + html: &str, + url: &str, + title: Option<&str>, + ) -> EngineResult { + #[derive(Serialize)] + struct ContentBlock { + #[serde(rename = "type")] + content_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + text: Option, + } + + #[derive(Serialize)] + struct Message { + role: String, + content: Vec, + } + + #[derive(Serialize)] + struct ResponseFormat { + #[serde(rename = "type")] + format_type: String, + } + + #[derive(Serialize)] + struct InferenceRequest { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + max_tokens: u32, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "response_format")] + response_format: Option, + #[serde(skip_serializing_if = "Option::is_none")] + reasoning: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thinking: Option, + /// Anthropic top-level system prompt (outside messages array). + #[serde(skip_serializing_if = "Option::is_none")] + system: Option, + } + + let Some(( + effective_cfg, + effective_system_prompt, + effective_system_prompt_extra, + effective_user_message_extra, + )) = self.resolve_runtime_for_url(url) + else { + return Ok(AutomationResult { + label: "url_not_allowed".into(), + steps_executed: 0, + success: true, + error: None, + usage: AutomationUsage::default(), + extracted: None, + screenshot: None, + spawn_pages: Vec::new(), + relevant: None, + reasoning: None, + }); + }; + + let mut prompt_engine = self.clone(); + prompt_engine.system_prompt = effective_system_prompt; + prompt_engine.system_prompt_extra = effective_system_prompt_extra; + + let is_anthropic = is_anthropic_endpoint(&self.api_url); + + // Build user prompt with HTML context + let mut user_text = + String::with_capacity(256 + html.len().min(effective_cfg.html_max_bytes)); + user_text.push_str("EXTRACTION CONTEXT:\n"); + user_text.push_str("- url: "); + user_text.push_str(url); + user_text.push('\n'); + if let Some(t) = title { + user_text.push_str("- title: "); + user_text.push_str(t); + user_text.push('\n'); + } + user_text.push_str("\nHTML CONTENT:\n"); + + // Truncate HTML if needed + let html_truncated = truncate_utf8_tail(html, effective_cfg.html_max_bytes); + user_text.push_str(&html_truncated); + + user_text.push_str( + "\n\nTASK:\nExtract structured data from the HTML above. Return a JSON object with:\n", + ); + user_text.push_str("- \"label\": short description of what was extracted\n"); + user_text.push_str("- \"done\": true\n"); + user_text.push_str("- \"steps\": [] (empty, no browser automation)\n"); + user_text.push_str("- \"extracted\": the structured data extracted from the page\n"); + if effective_cfg.relevance_gate { + user_text.push_str( + "- \"relevant\": true if page is relevant to the goal, false otherwise\n", + ); + } + + if let Some(extra) = &effective_user_message_extra { + if !extra.trim().is_empty() { + user_text.push_str("\n---\nUSER INSTRUCTIONS:\n"); + user_text.push_str(extra.trim()); + user_text.push('\n'); + } + } + + let system_text = prompt_engine.system_prompt_compiled(&effective_cfg); + + // Anthropic: system as top-level field, thinking instead of reasoning, + // no response_format, max_tokens includes thinking budget. + // Temperature is omitted when thinking is enabled (Anthropic requires it). + let thinking_pl = if is_anthropic { + effective_thinking_payload(&effective_cfg) + } else { + None + }; + let has_thinking = thinking_pl.is_some(); + + let messages = if is_anthropic { + // Anthropic: no system role in messages + vec![Message { + role: "user".into(), + content: vec![ContentBlock { + content_type: "text".into(), + text: Some(user_text), + }], + }] + } else { + vec![ + Message { + role: "system".into(), + content: vec![ContentBlock { + content_type: "text".into(), + text: Some(system_text.clone()), + }], + }, + Message { + role: "user".into(), + content: vec![ContentBlock { + content_type: "text".into(), + text: Some(user_text), + }], + }, + ] + }; + + let max_tokens = if let Some(budget) = thinking_pl + .as_ref() + .and_then(|v| v.get("budget_tokens")) + .and_then(|v| v.as_u64()) + { + effective_cfg.max_tokens as u32 + budget as u32 + } else { + effective_cfg.max_tokens as u32 + }; + + let request_body = InferenceRequest { + model: self.model_name.clone(), + messages, + temperature: if has_thinking { + None + } else { + Some(effective_cfg.temperature) + }, + max_tokens, + response_format: if is_anthropic || has_thinking { + None + } else if effective_cfg.request_json_object { + Some(ResponseFormat { + format_type: "json_object".into(), + }) + } else { + None + }, + reasoning: if is_anthropic { + None + } else { + reasoning_payload(&effective_cfg) + }, + thinking: thinking_pl, + system: if is_anthropic { + Some(system_text) + } else { + None + }, + }; + + // Acquire permit before sending + let _permit = self.acquire_llm_permit().await; + + let mut req = CLIENT.post(&self.api_url).json(&request_body); + if let Some(key) = &self.api_key { + req = req.bearer_auth(key); + } + + let start = std::time::Instant::now(); + let http_resp = req.send().await?; + let status = http_resp.status(); + let raw_body = http_resp.text().await?; + + log::debug!( + "remote_multimodal extract_from_html: status={} latency={:?} body_len={}", + status, + start.elapsed(), + raw_body.len() + ); + + if !status.is_success() { + return Err(EngineError::RemoteStatus( + status.as_u16(), + format!("non-success status {status}: {raw_body}"), + )); + } + + let root: serde_json::Value = serde_json::from_str(&raw_body) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))?; + + let content = extract_assistant_content(&root) + .ok_or(EngineError::MissingField("assistant content"))?; + + let usage = extract_usage(&root); + + let plan_value = if effective_cfg.best_effort_json_extract { + best_effort_parse_json_object(&content)? + } else { + serde_json::from_str::(&content) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))? + }; + + let label = plan_value + .get("label") + .and_then(|v| v.as_str()) + .unwrap_or("extraction") + .to_string(); + + // Extract relevance field if gate is enabled + let relevant = if effective_cfg.relevance_gate { + Some( + plan_value + .get("relevant") + .and_then(|v| v.as_bool()) + .unwrap_or(true), + ) + } else { + None + }; + + // Reasoning: prefer API-level thinking content (Anthropic/OpenAI thinking blocks), + // fall back to JSON-level "reasoning" field from the model's response. + let reasoning = extract_thinking_content(&root).or_else(|| { + plan_value.get("reasoning").and_then(|v| { + if let Some(s) = v.as_str() { + let trimmed = s.trim(); + return if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + }; + } + if v.is_null() { + None + } else { + Some(v.to_string()) + } + }) + }); + + // Try to get extracted field, or fallback to the entire response + let extracted = plan_value.get("extracted").cloned().or_else(|| { + // If no explicit "extracted" field but response looks like extracted data + // (has no standard automation fields), use the whole response + if plan_value.get("label").is_none() + && plan_value.get("done").is_none() + && plan_value.get("steps").is_none() + { + // Response doesn't have automation structure, treat as direct extraction + Some(plan_value.clone()) + } else { + // In extraction mode, if response has automation structure but no extracted, + // check if there's any non-automation data to extract + let mut extracted_data = serde_json::Map::new(); + if let Some(obj) = plan_value.as_object() { + for (key, value) in obj { + // Skip known automation fields + if !matches!( + key.as_str(), + "label" + | "done" + | "steps" + | "memory_ops" + | "extracted" + | "relevant" + | "reasoning" + ) { + extracted_data.insert(key.clone(), value.clone()); + } + } + } + if !extracted_data.is_empty() { + Some(serde_json::Value::Object(extracted_data)) + } else { + None + } + } + }); + + Ok(AutomationResult { + label, + steps_executed: 0, + success: true, + error: None, + usage, + extracted, + screenshot: None, + spawn_pages: Vec::new(), + relevant, + reasoning, + }) + } + + /// Extract structured data from HTML with an optional screenshot. + /// + /// This method combines HTML text with a screenshot for more accurate extraction, + /// especially useful for pages with visual content that isn't in the HTML + /// (iframes, videos, canvas, dynamically rendered content). + /// + /// # Arguments + /// * `html` - The raw HTML content + /// * `url` - The URL of the page (for context) + /// * `title` - Optional page title + /// * `screenshot_base64` - Optional base64-encoded screenshot (PNG/JPEG) + pub async fn extract_with_screenshot( + &self, + html: &str, + url: &str, + title: Option<&str>, + screenshot_base64: Option<&str>, + ) -> EngineResult { + #[derive(Serialize)] + struct ContentBlock { + #[serde(rename = "type")] + content_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + image_url: Option, + } + + #[derive(Serialize)] + struct ImageUrlBlock { + url: String, + } + + #[derive(Serialize)] + struct Message { + role: String, + content: Vec, + } + + #[derive(Serialize)] + struct ResponseFormat { + #[serde(rename = "type")] + format_type: String, + } + + #[derive(Serialize)] + struct InferenceRequest { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + max_tokens: u32, + #[serde(skip_serializing_if = "Option::is_none")] + response_format: Option, + #[serde(skip_serializing_if = "Option::is_none")] + reasoning: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thinking: Option, + #[serde(skip_serializing_if = "Option::is_none")] + system: Option, + } + + let Some(( + effective_cfg, + effective_system_prompt, + effective_system_prompt_extra, + effective_user_message_extra, + )) = self.resolve_runtime_for_url(url) + else { + return Ok(AutomationResult { + label: "url_not_allowed".into(), + steps_executed: 0, + success: true, + error: None, + usage: AutomationUsage::default(), + extracted: None, + screenshot: None, + spawn_pages: Vec::new(), + relevant: None, + reasoning: None, + }); + }; + + let mut prompt_engine = self.clone(); + prompt_engine.system_prompt = effective_system_prompt; + prompt_engine.system_prompt_extra = effective_system_prompt_extra; + + let is_anthropic = is_anthropic_endpoint(&self.api_url); + + // Build user prompt with HTML context + let mut user_text = + String::with_capacity(256 + html.len().min(effective_cfg.html_max_bytes)); + user_text.push_str("EXTRACTION CONTEXT:\n"); + user_text.push_str("- url: "); + user_text.push_str(url); + user_text.push('\n'); + if let Some(t) = title { + user_text.push_str("- title: "); + user_text.push_str(t); + user_text.push('\n'); + } + + // Analyze content and note if screenshot is being used + if screenshot_base64.is_some() { + user_text.push_str("- screenshot: provided (use for visual content not in HTML)\n"); + } + + user_text.push_str("\nHTML CONTENT:\n"); + let html_truncated = truncate_utf8_tail(html, effective_cfg.html_max_bytes); + user_text.push_str(&html_truncated); + + user_text.push_str("\n\nTASK:\nExtract structured data from the page. Use both the HTML and screenshot (if provided) to extract information. Return a JSON object with:\n"); + user_text.push_str("- \"label\": short description of what was extracted\n"); + user_text.push_str("- \"done\": true\n"); + user_text.push_str("- \"steps\": [] (empty, no browser automation)\n"); + user_text.push_str("- \"extracted\": the structured data extracted from the page\n"); + if effective_cfg.relevance_gate { + user_text.push_str( + "- \"relevant\": true if page is relevant to the goal, false otherwise\n", + ); + } + + if screenshot_base64.is_some() { + user_text.push_str("\nIMPORTANT: The screenshot may contain visual information not present in the HTML (iframe content, videos, canvas drawings, dynamically rendered content). Examine the screenshot carefully.\n"); + } + + if let Some(extra) = &effective_user_message_extra { + if !extra.trim().is_empty() { + user_text.push_str("\n---\nUSER INSTRUCTIONS:\n"); + user_text.push_str(extra.trim()); + user_text.push('\n'); + } + } + + // Build message content + let mut user_content = vec![ContentBlock { + content_type: "text".into(), + text: Some(user_text), + image_url: None, + }]; + + // Add screenshot if provided + if let Some(screenshot) = screenshot_base64 { + let image_url = if screenshot.starts_with("data:") { + screenshot.to_string() + } else { + format!("data:image/png;base64,{}", screenshot) + }; + user_content.push(ContentBlock { + content_type: "image_url".into(), + text: None, + image_url: Some(ImageUrlBlock { url: image_url }), + }); + } + + let system_text = prompt_engine.system_prompt_compiled(&effective_cfg); + let thinking_pl = if is_anthropic { + effective_thinking_payload(&effective_cfg) + } else { + None + }; + let has_thinking = thinking_pl.is_some(); + + let messages = if is_anthropic { + vec![Message { + role: "user".into(), + content: user_content, + }] + } else { + vec![ + Message { + role: "system".into(), + content: vec![ContentBlock { + content_type: "text".into(), + text: Some(system_text.clone()), + image_url: None, + }], + }, + Message { + role: "user".into(), + content: user_content, + }, + ] + }; + + let max_tokens = if let Some(budget) = thinking_pl + .as_ref() + .and_then(|v| v.get("budget_tokens")) + .and_then(|v| v.as_u64()) + { + effective_cfg.max_tokens as u32 + budget as u32 + } else { + effective_cfg.max_tokens as u32 + }; + + let request_body = InferenceRequest { + model: self.model_name.clone(), + messages, + temperature: if has_thinking { + None + } else { + Some(effective_cfg.temperature) + }, + max_tokens, + response_format: if is_anthropic || has_thinking { + None + } else if effective_cfg.request_json_object { + Some(ResponseFormat { + format_type: "json_object".into(), + }) + } else { + None + }, + reasoning: if is_anthropic { + None + } else { + reasoning_payload(&effective_cfg) + }, + thinking: thinking_pl, + system: if is_anthropic { + Some(system_text) + } else { + None + }, + }; + + let _permit = self.acquire_llm_permit().await; + + let mut req = CLIENT.post(&self.api_url).json(&request_body); + if let Some(key) = &self.api_key { + req = req.bearer_auth(key); + } + + let start = std::time::Instant::now(); + let http_resp = req.send().await?; + let status = http_resp.status(); + let raw_body = http_resp.text().await?; + + log::debug!( + "remote_multimodal extract_with_screenshot: status={} latency={:?} body_len={}", + status, + start.elapsed(), + raw_body.len() + ); + + if !status.is_success() { + return Err(EngineError::RemoteStatus( + status.as_u16(), + format!("non-success status {status}: {raw_body}"), + )); + } + + let root: serde_json::Value = serde_json::from_str(&raw_body) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))?; + + let content = extract_assistant_content(&root) + .ok_or(EngineError::MissingField("assistant content"))?; + + let usage = extract_usage(&root); + + let plan_value = if effective_cfg.best_effort_json_extract { + best_effort_parse_json_object(&content)? + } else { + serde_json::from_str::(&content) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))? + }; + + let label = plan_value + .get("label") + .and_then(|v| v.as_str()) + .unwrap_or("extraction") + .to_string(); + + // Extract relevance field if gate is enabled + let relevant = if effective_cfg.relevance_gate { + Some( + plan_value + .get("relevant") + .and_then(|v| v.as_bool()) + .unwrap_or(true), + ) + } else { + None + }; + + // Reasoning: prefer API-level thinking content, fall back to JSON field + let reasoning = extract_thinking_content(&root).or_else(|| { + plan_value.get("reasoning").and_then(|v| { + if let Some(s) = v.as_str() { + let trimmed = s.trim(); + return if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + }; + } + if v.is_null() { + None + } else { + Some(v.to_string()) + } + }) + }); + + // Try to get extracted field, or fallback to the entire response + let extracted = plan_value.get("extracted").cloned().or_else(|| { + // If no explicit "extracted" field but response looks like extracted data + // (has no standard automation fields), use the whole response + if plan_value.get("label").is_none() + && plan_value.get("done").is_none() + && plan_value.get("steps").is_none() + { + Some(plan_value.clone()) + } else { + // Extract non-automation fields + let mut extracted_data = serde_json::Map::new(); + if let Some(obj) = plan_value.as_object() { + for (key, value) in obj { + if !matches!( + key.as_str(), + "label" + | "done" + | "steps" + | "memory_ops" + | "extracted" + | "relevant" + | "reasoning" + ) { + extracted_data.insert(key.clone(), value.clone()); + } + } + } + if !extracted_data.is_empty() { + Some(serde_json::Value::Object(extracted_data)) + } else { + None + } + } + }); + + Ok(AutomationResult { + label, + steps_executed: 0, + success: true, + error: None, + usage, + extracted, + screenshot: None, + spawn_pages: Vec::new(), + relevant, + reasoning, + }) + } + + /// Send a raw chat completion request and get the response. + /// + /// This is a lower-level method for custom use cases. + pub async fn chat_completion( + &self, + system_prompt: &str, + user_message: &str, + ) -> EngineResult<(String, AutomationUsage)> { + #[derive(Serialize)] + struct Message { + role: String, + content: String, + } + + #[derive(Serialize)] + struct ResponseFormat { + #[serde(rename = "type")] + format_type: String, + } + + #[derive(Serialize)] + struct InferenceRequest { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + max_tokens: u32, + #[serde(skip_serializing_if = "Option::is_none")] + response_format: Option, + #[serde(skip_serializing_if = "Option::is_none")] + reasoning: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thinking: Option, + #[serde(skip_serializing_if = "Option::is_none")] + system: Option, + } + + let is_anthropic = is_anthropic_endpoint(&self.api_url); + let thinking_pl = if is_anthropic { + effective_thinking_payload(&self.cfg) + } else { + None + }; + let has_thinking = thinking_pl.is_some(); + + let messages = if is_anthropic { + vec![Message { + role: "user".into(), + content: user_message.to_string(), + }] + } else { + vec![ + Message { + role: "system".into(), + content: system_prompt.to_string(), + }, + Message { + role: "user".into(), + content: user_message.to_string(), + }, + ] + }; + + let max_tokens = if let Some(budget) = thinking_pl + .as_ref() + .and_then(|v| v.get("budget_tokens")) + .and_then(|v| v.as_u64()) + { + self.cfg.max_tokens as u32 + budget as u32 + } else { + self.cfg.max_tokens as u32 + }; + + let request_body = InferenceRequest { + model: self.model_name.clone(), + messages, + temperature: if has_thinking { + None + } else { + Some(self.cfg.temperature) + }, + max_tokens, + response_format: if is_anthropic || has_thinking { + None + } else if self.cfg.request_json_object { + Some(ResponseFormat { + format_type: "json_object".into(), + }) + } else { + None + }, + reasoning: if is_anthropic { + None + } else { + reasoning_payload(&self.cfg) + }, + thinking: thinking_pl, + system: if is_anthropic { + Some(system_prompt.to_string()) + } else { + None + }, + }; + + let _permit = self.acquire_llm_permit().await; + + let mut req = CLIENT.post(&self.api_url).json(&request_body); + if let Some(key) = &self.api_key { + req = req.bearer_auth(key); + } + + let http_resp = req.send().await?; + let status = http_resp.status(); + let raw_body = http_resp.text().await?; + + if !status.is_success() { + return Err(EngineError::RemoteStatus( + status.as_u16(), + format!("non-success status {status}: {raw_body}"), + )); + } + + let root: serde_json::Value = serde_json::from_str(&raw_body) + .map_err(|e| EngineError::Remote(format!("JSON parse error: {e}")))?; + + let content = extract_assistant_content(&root) + .ok_or(EngineError::MissingField("assistant content"))?; + + let usage = extract_usage(&root); + + Ok((content, usage)) + } + + // ===== URL Pre-filter Classification ===== + + /// Classify a batch of URLs as relevant or irrelevant using the text model. + /// Returns a `Vec` parallel to the input URLs (`true` = relevant). + /// + /// Uses `resolve_model_for_round(false)` to get the cheap/fast text model. + /// On any failure (HTTP, parse, length mismatch), returns all `true` (safe fallback). + pub async fn classify_urls( + &self, + urls: &[&str], + relevance_prompt: Option<&str>, + extraction_prompt: Option<&str>, + max_tokens: u16, + ) -> EngineResult> { + if urls.is_empty() { + return Ok(Vec::new()); + } + + let criteria = relevance_prompt + .or(extraction_prompt) + .unwrap_or("General web crawling"); + + let system = format!( + "You are a URL relevance classifier. Given a list of URLs, determine which are relevant to the crawl goal.\nGoal: {}\n\nRespond ONLY with a JSON array of 1s and 0s, one per URL. 1=relevant, 0=irrelevant.\nExample: [1,0,1,1,0]", + criteria + ); + + let mut user_msg = String::with_capacity(urls.len() * 80); + user_msg.push_str("Classify these URLs:\n"); + for (i, url) in urls.iter().enumerate() { + user_msg.push_str(&format!("{}. {}\n", i + 1, url)); + } + + // Use the text model endpoint (cheap/fast) + let (api_url, model_name, api_key) = self.resolve_model_for_round(false); + let is_anthropic = is_anthropic_endpoint(api_url); + + #[derive(Serialize)] + struct Message { + role: String, + content: String, + } + #[derive(Serialize)] + struct InferenceRequest { + model: String, + messages: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + temperature: Option, + max_tokens: u32, + #[serde(skip_serializing_if = "Option::is_none")] + reasoning: Option, + #[serde(skip_serializing_if = "Option::is_none")] + thinking: Option, + #[serde(skip_serializing_if = "Option::is_none")] + system: Option, + } + + let thinking_pl = if is_anthropic { + effective_thinking_payload(&self.cfg) + } else { + None + }; + let has_thinking = thinking_pl.is_some(); + + let classify_max_tokens = if let Some(budget) = thinking_pl + .as_ref() + .and_then(|v| v.get("budget_tokens")) + .and_then(|v| v.as_u64()) + { + max_tokens as u32 + budget as u32 + } else { + max_tokens as u32 + }; + + let messages = if is_anthropic { + vec![Message { + role: "user".into(), + content: user_msg, + }] + } else { + vec![ + Message { + role: "system".into(), + content: system.clone(), + }, + Message { + role: "user".into(), + content: user_msg, + }, + ] + }; + + let request_body = InferenceRequest { + model: model_name.to_string(), + messages, + temperature: if has_thinking { None } else { Some(0.0) }, + max_tokens: classify_max_tokens, + reasoning: if is_anthropic { + None + } else { + reasoning_payload(&self.cfg) + }, + thinking: thinking_pl, + system: if is_anthropic { Some(system) } else { None }, + }; + + let _permit = self.acquire_llm_permit().await; + + let mut req = CLIENT.post(api_url).json(&request_body); + if let Some(key) = api_key { + req = req.bearer_auth(key); + } + + let http_resp = match req.send().await { + Ok(r) => r, + Err(e) => { + log::warn!("url_prefilter: HTTP error, assuming all relevant: {e}"); + return Ok(vec![true; urls.len()]); + } + }; + + if !http_resp.status().is_success() { + log::warn!( + "url_prefilter: non-success status {}, assuming all relevant", + http_resp.status() + ); + return Ok(vec![true; urls.len()]); + } + + let raw_body = http_resp.text().await.unwrap_or_default(); + let root: serde_json::Value = match serde_json::from_str(&raw_body) { + Ok(v) => v, + Err(_) => return Ok(vec![true; urls.len()]), + }; + + let content = match extract_assistant_content(&root) { + Some(c) => c, + None => return Ok(vec![true; urls.len()]), + }; + + Ok(parse_url_classifications(&content, urls.len())) + } + + // ===== New Feature Integration Methods ===== + + /// Generate an extraction schema from example data. + /// + /// Uses the schema generation utilities to create a JSON schema + /// from example outputs. Useful for zero-config extraction setup. + pub fn generate_schema_from_examples( + &self, + examples: &[serde_json::Value], + name: Option<&str>, + description: Option<&str>, + ) -> super::GeneratedSchema { + let request = super::SchemaGenerationRequest { + examples: examples.to_vec(), + description: description.map(|s| s.to_string()), + strict: false, + name: name.map(|s| s.to_string()), + }; + super::generate_schema(&request) + } + + /// Infer a JSON schema from a single example value. + pub fn infer_schema(&self, example: &serde_json::Value) -> serde_json::Value { + super::infer_schema(example) + } + + /// Build a schema generation prompt for LLM-assisted schema creation. + pub fn build_schema_prompt( + &self, + examples: &[serde_json::Value], + description: Option<&str>, + ) -> String { + let request = super::SchemaGenerationRequest { + examples: examples.to_vec(), + description: description.map(|s| s.to_string()), + strict: false, + name: None, + }; + super::build_schema_generation_prompt(&request) + } + + /// Parse tool calls from an LLM response. + /// + /// Extracts OpenAI-compatible tool calls from a response JSON. + pub fn parse_tool_calls(&self, response: &serde_json::Value) -> Vec { + super::parse_tool_calls(response) + } + + /// Convert tool calls to automation step actions. + pub fn tool_calls_to_steps(&self, calls: &[super::ToolCall]) -> Vec { + super::tool_calls_to_steps(calls) + } + + /// Get all available action tool schemas. + /// + /// Returns OpenAI-compatible tool definitions for all supported actions. + pub fn action_tool_schemas(&self) -> Vec { + super::ActionToolSchemas::all() + } + + /// Extract HTML context around selectors for self-healing. + pub fn extract_html_context(&self, html: &str, max_bytes: usize) -> String { + super::extract_html_context(html, max_bytes) + } + + /// Create a new dependency graph for concurrent execution. + pub fn create_dependency_graph( + &self, + steps: Vec, + ) -> Result { + super::DependencyGraph::new(steps) + } + + /// Execute a dependency graph with the provided executor. + /// + /// This enables parallel execution of independent steps using `tokio::JoinSet`. + pub async fn execute_dependency_graph( + &self, + graph: &mut super::DependencyGraph, + config: &super::ConcurrentChainConfig, + executor: F, + ) -> super::ConcurrentChainResult + where + F: Fn(super::DependentStep) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send + 'static, + { + super::execute_graph(graph, config, executor).await + } +} + +/// Parse a URL classification response into a `Vec`. +/// +/// Expects a JSON array of 0/1 integers, e.g. `[1,0,1,1,0]`. +/// On parse failure or length mismatch, returns all `true` (safe fallback). +fn parse_url_classifications(response: &str, expected_len: usize) -> Vec { + // Try to find a JSON array in the response + let trimmed = response.trim(); + + // Find the array boundaries + let start = match trimmed.find('[') { + Some(i) => i, + None => return vec![true; expected_len], + }; + let end = match trimmed.rfind(']') { + Some(i) => i + 1, + None => return vec![true; expected_len], + }; + + let arr_str = &trimmed[start..end]; + + let arr: Vec = match serde_json::from_str(arr_str) { + Ok(v) => v, + Err(_) => return vec![true; expected_len], + }; + + if arr.len() != expected_len { + log::warn!( + "url_prefilter: classification length mismatch (got {}, expected {}), assuming all relevant", + arr.len(), + expected_len + ); + return vec![true; expected_len]; + } + + arr.iter() + .map(|v| { + // Accept 1/0 as integers or booleans + v.as_i64().map(|n| n != 0).unwrap_or_else(|| { + v.as_bool().unwrap_or(true) // default to relevant + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_url_classifications_valid() { + assert_eq!( + parse_url_classifications("[1,0,1]", 3), + vec![true, false, true] + ); + } + + #[test] + fn test_parse_url_classifications_booleans() { + assert_eq!( + parse_url_classifications("[true,false,true]", 3), + vec![true, false, true] + ); + } + + #[test] + fn test_parse_url_classifications_length_mismatch() { + // Length mismatch → all true + assert_eq!( + parse_url_classifications("[1,0]", 3), + vec![true, true, true] + ); + } + + #[test] + fn test_parse_url_classifications_invalid_json() { + assert_eq!(parse_url_classifications("not json", 2), vec![true, true]); + } + + #[test] + fn test_parse_url_classifications_embedded_array() { + // Array embedded in surrounding text + assert_eq!( + parse_url_classifications("Here are the results: [1,0,1,0]", 4), + vec![true, false, true, false] + ); + } + + #[test] + fn test_parse_url_classifications_empty() { + assert_eq!(parse_url_classifications("[]", 0), Vec::::new()); + } + + #[test] + fn test_engine_new() { + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + + assert_eq!(engine.api_url, "https://api.openai.com/v1/chat/completions"); + assert_eq!(engine.model_name, "gpt-4o"); + assert!(engine.api_key.is_none()); + assert!(engine.system_prompt.is_none()); + } + + #[test] + fn test_engine_with_api_key() { + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ) + .with_api_key(Some("sk-test")); + + assert_eq!(engine.api_key, Some("sk-test".to_string())); + } + + #[test] + fn test_engine_system_prompt_compiled() { + // System prompt is locked to DEFAULT_SYSTEM_PROMPT + // Custom instructions go through system_prompt_extra + let mut engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + engine.with_system_prompt_extra(Some("Custom instructions")); + + let compiled = engine.system_prompt_compiled(&RemoteMultimodalConfig::default()); + assert!(compiled.starts_with(super::DEFAULT_SYSTEM_PROMPT)); + assert!(compiled.contains("Custom instructions")); + assert!(compiled.contains("RUNTIME CONFIG")); + } + + #[test] + fn test_engine_system_prompt_with_extraction() { + let cfg = RemoteMultimodalConfig { + extra_ai_data: true, + extraction_schema: Some(ExtractionSchema::new("products", r#"{"type":"array"}"#)), + ..Default::default() + }; + + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + + let compiled = engine.system_prompt_compiled(&cfg); + assert!(compiled.contains("EXTRACTION MODE ENABLED")); + assert!(compiled.contains("products")); + } + + #[test] + fn test_engine_system_prompt_extraction_only() { + let cfg = RemoteMultimodalConfig::new() + .with_extraction(true) + .with_max_rounds(1); + assert!(cfg.is_extraction_only()); + + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + + let compiled = engine.system_prompt_compiled(&cfg); + // Should use focused extraction prompt, not full automation prompt + assert!(compiled.contains("data extraction assistant")); + assert!(!compiled.contains("ClickPoint")); + assert!(!compiled.contains("SetViewport")); + } + + #[test] + fn test_engine_analyze_content() { + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + + let html = "

Test content

"; + let analysis = engine.analyze_content(html); + assert!(!analysis.has_visual_elements); + } + + #[test] + fn test_engine_needs_screenshot() { + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ); + + assert!(engine.needs_screenshot("")); + assert!(!engine.needs_screenshot(&"a".repeat(2000))); + } + + #[test] + fn test_engine_clone_with_cfg() { + let engine = RemoteMultimodalEngine::new( + "https://api.openai.com/v1/chat/completions", + "gpt-4o", + None, + ) + .with_api_key(Some("sk-test")); + + let new_cfg = RemoteMultimodalConfig { + max_rounds: 10, + ..Default::default() + }; + + let cloned = engine.clone_with_cfg(new_cfg); + assert_eq!(cloned.api_key, Some("sk-test".to_string())); + assert_eq!(cloned.cfg.max_rounds, 10); + } + + // ── Dual-model routing engine tests ────────────────────────────── + + #[test] + fn test_engine_dual_model_routing_setup() { + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + assert!(!engine.has_dual_model_routing()); + + engine.with_vision_model(Some(crate::automation::ModelEndpoint::new("gpt-4o"))); + engine.with_text_model(Some(crate::automation::ModelEndpoint::new("gpt-4o-mini"))); + assert!(engine.has_dual_model_routing()); + } + + #[test] + fn test_engine_resolve_model_for_round() { + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "primary", None); + engine.api_key = Some("sk-parent".to_string()); + engine.with_vision_model(Some(crate::automation::ModelEndpoint::new("vision-model"))); + engine.with_text_model(Some( + crate::automation::ModelEndpoint::new("text-model") + .with_api_url("https://text.api.com") + .with_api_key("sk-text"), + )); + + // Vision round → vision model, inherits parent URL/key + let (url, model, key) = engine.resolve_model_for_round(true); + assert_eq!(model, "vision-model"); + assert_eq!(url, "https://api.example.com"); + assert_eq!(key, Some("sk-parent")); + + // Text round → text model with its own URL/key + let (url, model, key) = engine.resolve_model_for_round(false); + assert_eq!(model, "text-model"); + assert_eq!(url, "https://text.api.com"); + assert_eq!(key, Some("sk-text")); + } + + #[test] + fn test_engine_should_use_vision_this_round() { + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.with_vision_model(Some(crate::automation::ModelEndpoint::new("gpt-4o"))); + engine.with_text_model(Some(crate::automation::ModelEndpoint::new("gpt-4o-mini"))); + engine.with_vision_route_mode(crate::automation::VisionRouteMode::TextFirst); + + // Round 0 → vision + assert!(engine.should_use_vision_this_round(0, false, 0, false)); + // Round 1+ → text + assert!(!engine.should_use_vision_this_round(1, false, 0, false)); + // Stagnation → vision + assert!(engine.should_use_vision_this_round(3, true, 0, false)); + // Stuck ≥ 3 → vision + assert!(engine.should_use_vision_this_round(3, false, 3, false)); + // Force → vision + assert!(engine.should_use_vision_this_round(5, false, 0, true)); + } + + #[test] + fn test_engine_no_routing_always_vision() { + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + // No dual routing → always returns true + assert!(!engine.has_dual_model_routing()); + assert!(engine.should_use_vision_this_round(0, false, 0, false)); + assert!(engine.should_use_vision_this_round(99, false, 0, false)); + } + + #[test] + fn test_engine_system_prompt_extraction_only_with_schema() { + let mut cfg = RemoteMultimodalConfig::new() + .with_extraction(true) + .with_max_rounds(1); + cfg.extraction_schema = Some(ExtractionSchema::new("products", r#"{"type":"array"}"#)); + cfg.extraction_prompt = Some("Extract all products".to_string()); + + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + + let compiled = engine.system_prompt_compiled(&cfg); + // Should use focused prompt + assert!(compiled.contains("data extraction assistant")); + // Should include extraction schema and prompt + assert!(compiled.contains("EXTRACTION MODE ENABLED")); + assert!(compiled.contains("products")); + assert!(compiled.contains("Extract all products")); + } + + #[test] + fn test_engine_system_prompt_multi_round_extraction_uses_default() { + // extra_ai_data=true but max_rounds=6 → NOT extraction-only + let cfg = RemoteMultimodalConfig { + extra_ai_data: true, + ..Default::default() + }; + assert!(!cfg.is_extraction_only()); + + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + + let compiled = engine.system_prompt_compiled(&cfg); + // Should use full automation prompt + assert!(compiled.contains("ClickPoint")); + assert!(compiled.contains("SetViewport")); + assert!(compiled.contains("EXTRACTION MODE ENABLED")); + } + + #[test] + fn test_engine_resolve_runtime_for_url_override() { + let mut url_map = std::collections::HashMap::new(); + let override_cfg = crate::automation::AutomationConfig::new("override goal") + .with_max_steps(2) + .with_retries(5) + .with_system_prompt("override system") + .with_system_prompt_extra("override extra") + .with_user_message_extra("override user") + .with_extraction("extract fields"); + url_map.insert("https://example.com".to_string(), Box::new(override_cfg)); + let gate = crate::automation::PromptUrlGate::with_map(url_map); + + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.with_prompt_url_gate(Some(gate)); + + let resolved = engine + .resolve_runtime_for_url("https://example.com") + .expect("url should be allowed"); + let (cfg, system_prompt, system_prompt_extra, user_message_extra) = resolved; + assert_eq!(cfg.max_rounds, 2); + assert_eq!(cfg.retry.max_attempts, 5); + assert!(cfg.extra_ai_data); + assert_eq!(cfg.extraction_prompt.as_deref(), Some("extract fields")); + assert_eq!(system_prompt.as_deref(), Some("override system")); + assert_eq!(system_prompt_extra.as_deref(), Some("override extra")); + assert_eq!(user_message_extra.as_deref(), Some("override user")); + } + + #[test] + fn test_engine_resolve_runtime_for_url_blocked() { + let mut url_map = std::collections::HashMap::new(); + url_map.insert( + "https://allowed.com".to_string(), + Box::new(crate::automation::AutomationConfig::new("allowed")), + ); + let gate = crate::automation::PromptUrlGate::with_map(url_map); + + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.with_prompt_url_gate(Some(gate)); + + assert!(engine + .resolve_runtime_for_url("https://blocked.com") + .is_none()); + } + + // ── Pool routing tests ─────────────────────────────────────────── + + #[test] + fn test_pool_routing_no_router_delegates() { + // No model_router → delegates to resolve_model_for_round + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None) + .with_api_key(Some("sk-test")); + assert!(engine.model_router.is_none()); + + let (url, model, key) = + engine.resolve_model_for_round_with_complexity(true, "click button", 500, 3, false); + assert_eq!(url, "https://api.example.com"); + assert_eq!(model, "gpt-4o"); + assert_eq!(key, Some("sk-test")); + } + + #[test] + fn test_pool_routing_picks_cheap_for_simple() { + use crate::automation::router::auto_policy; + + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "deepseek-chat"]); + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.model_router = Some(crate::automation::router::ModelRouter::with_policy( + policy.clone(), + )); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("deepseek-chat"), + ]; + + // Simple round (round 3, no stagnation, small HTML, no reasoning keywords) + let (_, model, _) = + engine.resolve_model_for_round_with_complexity(false, "click button", 500, 3, false); + // Should pick the small/cheap model + assert_eq!(model, policy.small, "simple round should use cheap model"); + } + + #[test] + fn test_pool_routing_picks_expensive_for_complex() { + use crate::automation::router::auto_policy; + + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "deepseek-chat"]); + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.model_router = Some(crate::automation::router::ModelRouter::with_policy( + policy.clone(), + )); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("deepseek-chat"), + ]; + + // Complex round: round 0, stagnated, large HTML, reasoning+code keywords + // In production user_text includes HTML so it's very long — simulate with a + // large prompt that also triggers multiple complexity indicators. + let long_prompt = "a]".repeat(9000); // ~18k chars → ~4500 tokens → above large_model_threshold + let (_, model, _) = engine.resolve_model_for_round_with_complexity( + false, + &format!("analyze and implement code to fix: {long_prompt}"), + 60_000, + 0, + true, // stagnated + ); + // Should pick the large/expensive model + assert_eq!( + model, policy.large, + "complex round should use powerful model" + ); + } + + #[test] + fn test_pool_routing_stagnated_upgrades() { + use crate::automation::router::auto_policy; + + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "deepseek-chat"]); + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.model_router = Some(crate::automation::router::ModelRouter::with_policy( + policy.clone(), + )); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("deepseek-chat"), + ]; + + // Stagnated round with simple prompt should still upgrade + let (_, model, _) = + engine.resolve_model_for_round_with_complexity(false, "click button", 500, 5, true); + // Stagnation forces requires_reasoning + multi_step → should not use cheapest + assert_ne!( + model, policy.small, + "stagnated round should not use cheapest model" + ); + } + + #[test] + fn test_pool_routing_vision_fallback() { + use crate::automation::router::auto_policy; + + // deepseek-chat does NOT support vision, gpt-4o does + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "deepseek-chat"]); + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.model_router = Some(crate::automation::router::ModelRouter::with_policy(policy)); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("deepseek-chat"), + ]; + + // Simple round would pick cheap model (deepseek-chat) but vision is needed + let (_, model, _) = + engine.resolve_model_for_round_with_complexity(true, "click button", 500, 3, false); + // deepseek-chat doesn't support vision → should fall back to a vision-capable model + assert!( + llm_models_spider::supports_vision(model), + "vision round should resolve to a vision-capable model, got {model}" + ); + } + + #[test] + fn test_pool_routing_inherits_endpoint_keys() { + use crate::automation::router::auto_policy; + + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "deepseek-chat"]); + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.api_key = Some("sk-parent".to_string()); + engine.model_router = Some(crate::automation::router::ModelRouter::with_policy(policy)); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("deepseek-chat") + .with_api_url("https://api.deepseek.com/v1/chat/completions") + .with_api_key("sk-ds"), + ]; + + // If routed to deepseek-chat, should use its endpoint-specific URL/key + // If routed to gpt-4o, should inherit parent URL/key + let (url, model, key) = engine.resolve_model_for_round_with_complexity( + false, + "analyze complex page with code", + 60_000, + 0, + false, + ); + if model == "deepseek-chat" { + assert_eq!(url, "https://api.deepseek.com/v1/chat/completions"); + assert_eq!(key, Some("sk-ds")); + } else { + // Inherited from parent + assert_eq!(url, "https://api.example.com"); + assert_eq!(key, Some("sk-parent")); + } + } + + #[test] + fn test_pick_fallback_model_skips_tried() { + let mut engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + engine.api_key = Some("sk-test".to_string()); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("gpt-4o"), + crate::automation::ModelEndpoint::new("claude-sonnet-4-20250514") + .with_api_url("https://api.anthropic.com/v1/messages") + .with_api_key("sk-ant"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + ]; + + // First fallback should skip gpt-4o (already tried) + let tried = vec!["gpt-4o".to_string()]; + let fallback = engine.pick_fallback_model(&tried, false); + assert!(fallback.is_some()); + let (_, model, _) = fallback.unwrap(); + assert_ne!(model, "gpt-4o", "should not re-pick already-tried model"); + + // After trying all models, should return None + let tried_all = vec![ + "gpt-4o".to_string(), + "claude-sonnet-4-20250514".to_string(), + "gpt-4o-mini".to_string(), + ]; + assert!(engine.pick_fallback_model(&tried_all, false).is_none()); + } + + #[test] + fn test_pick_fallback_model_prefers_vision_when_needed() { + let mut engine = + RemoteMultimodalEngine::new("https://api.example.com", "deepseek-chat", None); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("deepseek-chat"), + crate::automation::ModelEndpoint::new("gpt-4o-mini"), + crate::automation::ModelEndpoint::new("gpt-4o"), + ]; + + let tried = vec!["deepseek-chat".to_string()]; + let fallback = engine.pick_fallback_model(&tried, true); + assert!(fallback.is_some()); + let (_, model, _) = fallback.unwrap(); + // Should prefer a vision-capable model + assert!( + llm_models_spider::supports_vision(&model), + "should pick vision-capable model for vision round, got {model}" + ); + } + + #[test] + fn test_pick_fallback_model_inherits_endpoint_config() { + let mut engine = RemoteMultimodalEngine::new("https://api.default.com", "model-a", None); + engine.api_key = Some("sk-default".to_string()); + engine.model_pool = vec![ + crate::automation::ModelEndpoint::new("model-a"), + crate::automation::ModelEndpoint::new("model-b") + .with_api_url("https://api.custom.com") + .with_api_key("sk-custom"), + ]; + + let tried = vec!["model-a".to_string()]; + let fallback = engine.pick_fallback_model(&tried, false); + let (url, model, key) = fallback.unwrap(); + assert_eq!(model, "model-b"); + assert_eq!(url, "https://api.custom.com"); + assert_eq!(key, Some("sk-custom".to_string())); + } + + #[test] + fn test_pick_fallback_model_empty_pool() { + let engine = RemoteMultimodalEngine::new("https://api.example.com", "gpt-4o", None); + // model_pool is empty by default + assert!(engine.pick_fallback_model(&[], false).is_none()); + } + + #[test] + fn test_engine_error_retryable_status_codes() { + use crate::automation::EngineError; + assert!( + EngineError::RemoteStatus(502, "bad gateway".into()).is_retryable_on_different_model() + ); + assert!( + EngineError::RemoteStatus(503, "unavailable".into()).is_retryable_on_different_model() + ); + assert!( + EngineError::RemoteStatus(429, "rate limit".into()).is_retryable_on_different_model() + ); + assert!(EngineError::RemoteStatus(500, "internal".into()).is_retryable_on_different_model()); + assert!(EngineError::RemoteStatus(504, "timeout".into()).is_retryable_on_different_model()); + // 4xx client errors should NOT be retryable on a different model + assert!( + !EngineError::RemoteStatus(400, "bad request".into()).is_retryable_on_different_model() + ); + assert!(!EngineError::RemoteStatus(401, "unauthorized".into()) + .is_retryable_on_different_model()); + assert!( + !EngineError::RemoteStatus(403, "forbidden".into()).is_retryable_on_different_model() + ); + assert!( + !EngineError::RemoteStatus(404, "not found".into()).is_retryable_on_different_model() + ); + // Parse/field errors should NOT be retryable + assert!(!EngineError::MissingField("test").is_retryable_on_different_model()); + assert!(!EngineError::InvalidField("test").is_retryable_on_different_model()); + } +} diff --git a/spider_agent/src/automation/engine_error.rs b/spider_agent/src/automation/engine_error.rs new file mode 100644 index 000000000..68c00a7e6 --- /dev/null +++ b/spider_agent/src/automation/engine_error.rs @@ -0,0 +1,96 @@ +//! Engine error types for automation. + +use std::{error::Error as StdError, fmt}; + +/// Convenience result type used throughout the remote multimodal engine. +pub type EngineResult = Result; + +/// Errors produced by the remote multimodal engine. +/// +/// This error type is intentionally lightweight and is suitable +/// for surfacing from public APIs. +/// +/// It covers: +/// - transport failures when calling the remote endpoint, +/// - JSON serialization/deserialization failures, +/// - schema mismatches in OpenAI-compatible responses, +/// - non-success responses returned by the remote provider, +/// - unsupported operations due to compile-time feature flags. +#[derive(Debug)] +pub enum EngineError { + /// HTTP-layer failure (request could not be sent, connection error, timeout, etc.). + Http(reqwest::Error), + /// JSON serialization/deserialization failure when building or parsing payloads. + Json(serde_json::Error), + /// A required field was missing in a parsed JSON payload. + /// + /// Example: missing `"choices[0].message.content"` in an OpenAI-compatible response. + MissingField(&'static str), + /// A field was present but had an unexpected type or shape. + /// + /// Example: `"steps"` exists but is not an array. + InvalidField(&'static str), + /// The remote endpoint returned a non-success status or a server-side error. + /// + /// The contained string should be a human-readable explanation suitable for logs. + Remote(String), + /// The remote endpoint returned a non-success HTTP status with a known status code. + /// + /// Carries the numeric status code (e.g. 502, 429) so retry logic can + /// distinguish transient/retryable errors from permanent ones. + RemoteStatus(u16, String), + /// The operation is not supported in the current build configuration. + /// + /// Example: calling browser automation without the `chrome` feature. + Unsupported(&'static str), +} + +impl fmt::Display for EngineError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EngineError::Http(e) => write!(f, "http error: {e}"), + EngineError::Json(e) => write!(f, "json error: {e}"), + EngineError::MissingField(s) => write!(f, "missing field: {s}"), + EngineError::InvalidField(s) => write!(f, "invalid field: {s}"), + EngineError::Remote(s) => write!(f, "remote error: {s}"), + EngineError::RemoteStatus(code, s) => write!(f, "remote error {code}: {s}"), + EngineError::Unsupported(s) => write!(f, "unsupported: {s}"), + } + } +} + +impl StdError for EngineError {} + +impl EngineError { + /// Whether this error is transient and warrants retrying on a different model. + /// + /// Returns `true` for server errors (500, 502, 503), rate limits (429), + /// and transport-level failures (timeouts, connection resets). + pub fn is_retryable_on_different_model(&self) -> bool { + match self { + EngineError::RemoteStatus(code, _) => matches!(code, 429 | 500 | 502 | 503 | 504), + EngineError::Http(e) => e.is_timeout() || e.is_connect() || e.is_request(), + // Legacy Remote — check if message contains status hint + EngineError::Remote(msg) => { + msg.contains("502") + || msg.contains("503") + || msg.contains("429") + || msg.contains("500") + || msg.contains("504") + } + _ => false, + } + } +} + +impl From for EngineError { + fn from(e: reqwest::Error) -> Self { + EngineError::Http(e) + } +} + +impl From for EngineError { + fn from(e: serde_json::Error) -> Self { + EngineError::Json(e) + } +} diff --git a/spider_agent/src/automation/executor.rs b/spider_agent/src/automation/executor.rs new file mode 100644 index 000000000..9f9a7a6c0 --- /dev/null +++ b/spider_agent/src/automation/executor.rs @@ -0,0 +1,959 @@ +//! High-performance automation executor. +//! +//! Provides optimized execution patterns for automation workflows: +//! - Parallel step execution for independent operations +//! - Batched processing for multiple items +//! - Smart caching for LLM responses +//! - Prefetching for predictable navigation + +use super::{ChainCondition, ChainContext, ChainResult, ChainStep, ChainStepResult}; +use dashmap::{mapref::entry::Entry, DashMap}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::Semaphore; + +/// High-performance executor for automation chains. +/// +/// Features: +/// - Parallel execution of independent steps +/// - Response caching with TTL +/// - Configurable concurrency limits +/// - Progress tracking and cancellation +#[derive(Debug)] +pub struct ChainExecutor { + /// Maximum concurrent operations. + max_concurrency: usize, + /// Semaphore for concurrency control. + semaphore: Arc, + /// Response cache. + cache: Arc, + /// Whether caching is enabled. + cache_enabled: bool, + /// Default timeout per step. + step_timeout: Duration, +} + +impl Default for ChainExecutor { + fn default() -> Self { + Self::new() + } +} + +impl ChainExecutor { + /// Create a new executor with default settings. + pub fn new() -> Self { + let max_concurrency = 5; + Self { + max_concurrency, + semaphore: Arc::new(Semaphore::new(max_concurrency)), + cache: Arc::new(ResponseCache::new()), + cache_enabled: true, + step_timeout: Duration::from_secs(30), + } + } + + /// Create with custom concurrency limit. + pub fn with_concurrency(max_concurrency: usize) -> Self { + Self { + max_concurrency, + semaphore: Arc::new(Semaphore::new(max_concurrency)), + cache: Arc::new(ResponseCache::new()), + cache_enabled: true, + step_timeout: Duration::from_secs(30), + } + } + + /// Set step timeout. + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.step_timeout = timeout; + self + } + + /// Enable or disable caching. + pub fn with_cache(mut self, enabled: bool) -> Self { + self.cache_enabled = enabled; + self + } + + /// Get the maximum concurrency limit. + pub fn max_concurrency(&self) -> usize { + self.max_concurrency + } + + /// Execute a chain of steps. + /// + /// Steps are analyzed for dependencies and independent steps + /// are executed in parallel where possible. + pub async fn execute( + &self, + steps: Vec, + mut context: ChainContext, + step_fn: F, + ) -> ChainResult + where + F: Fn(ChainStep, ChainContext) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send, + { + let start = Instant::now(); + let mut result = ChainResult::new(); + let _total_steps = steps.len(); + + // Group steps by dependency + let groups = self.analyze_dependencies(&steps, &context); + + for group in groups { + if group.len() == 1 { + // Single step - execute directly + let step = group.into_iter().next().unwrap(); + let step_result = self.execute_step(step, context.clone(), &step_fn).await; + + context.set_previous_result(step_result.success); + context.advance(); + result.add_step(step_result); + } else { + // Multiple independent steps - execute in parallel + let step_results = self + .execute_parallel(group, context.clone(), &step_fn) + .await; + + let all_succeeded = step_results.iter().all(|r| r.success || !r.executed); + context.set_previous_result(all_succeeded); + + for step_result in step_results { + context.advance(); + result.add_step(step_result); + } + } + + // Check for abort conditions + if result.steps_failed > 0 && !self.should_continue(&result) { + break; + } + } + + result.duration_ms = start.elapsed().as_millis() as u64; + result.success = result.steps_failed == 0; + result + } + + /// Execute a single step with timeout and caching. + async fn execute_step( + &self, + step: ChainStep, + context: ChainContext, + step_fn: &F, + ) -> ChainStepResult + where + F: Fn(ChainStep, ChainContext) -> Fut + Clone + Send + Sync, + Fut: std::future::Future + Send, + { + let index = context.step_index; + let cache_key = self.cache_enabled.then(|| self.cache_key(&step, &context)); + + // Check condition + if !step.should_execute(&context) { + return ChainStepResult::skipped(index, &step.instruction); + } + + // Check cache if enabled + if let Some(cache_key) = cache_key.as_deref() { + if let Some(cached) = self.get_cached(cache_key).await { + return cached; + } + } + + // Acquire semaphore permit + let _permit = match self.semaphore.acquire().await { + Ok(p) => Some(p), + Err(_) => { + log::warn!("Semaphore closed, proceeding without permit"); + None + } + }; + + // Execute with timeout + let timeout = step + .timeout_ms + .map(Duration::from_millis) + .unwrap_or(self.step_timeout); + let step_clone = step.clone(); + + let result = tokio::time::timeout(timeout, step_fn(step_clone, context)).await; + + let step_result = match result { + Ok(r) => r, + Err(_) => ChainStepResult::executed(index, &step.instruction, false) + .with_error("Step timed out"), + }; + + // Cache successful results + if step_result.success { + if let Some(cache_key) = cache_key.as_deref() { + self.set_cached(cache_key, step_result.clone()).await; + } + } + + step_result + } + + /// Execute multiple steps in parallel. + async fn execute_parallel( + &self, + steps: Vec, + context: ChainContext, + step_fn: &F, + ) -> Vec + where + F: Fn(ChainStep, ChainContext) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send, + { + let mut handles = Vec::with_capacity(steps.len()); + + for (i, step) in steps.into_iter().enumerate() { + let ctx = ChainContext { + step_index: context.step_index + i, + ..context.clone() + }; + let semaphore = self.semaphore.clone(); + let timeout = step + .timeout_ms + .map(Duration::from_millis) + .unwrap_or(self.step_timeout); + let step_fn = step_fn.clone(); + + handles.push(tokio::spawn(async move { + let _permit = match semaphore.acquire().await { + Ok(p) => Some(p), + Err(_) => { + log::warn!("Semaphore closed, proceeding without permit"); + None + } + }; + + if !step.should_execute(&ctx) { + return ChainStepResult::skipped(ctx.step_index, &step.instruction); + } + + let step_clone = step.clone(); + let result = tokio::time::timeout(timeout, step_fn(step_clone, ctx.clone())).await; + + match result { + Ok(r) => r, + Err(_) => ChainStepResult::executed(ctx.step_index, &step.instruction, false) + .with_error("Step timed out"), + } + })); + } + + let mut results = Vec::with_capacity(handles.len()); + for handle in handles { + if let Ok(result) = handle.await { + results.push(result); + } + } + + // Sort by index to maintain order + results.sort_by_key(|r| r.index); + results + } + + /// Analyze steps for dependencies and group independent ones. + fn analyze_dependencies( + &self, + steps: &[ChainStep], + _context: &ChainContext, + ) -> Vec> { + let mut groups: Vec> = Vec::new(); + let mut current_group: Vec = Vec::new(); + + for step in steps { + let depends_on_previous = matches!( + &step.condition, + Some(ChainCondition::PreviousSucceeded) | Some(ChainCondition::PreviousFailed) + ); + + if depends_on_previous && !current_group.is_empty() { + // This step depends on previous - start new group + groups.push(std::mem::take(&mut current_group)); + } + + current_group.push(step.clone()); + + // If step modifies state significantly, flush group + if step.extract.is_some() || !step.continue_on_failure { + groups.push(std::mem::take(&mut current_group)); + } + } + + if !current_group.is_empty() { + groups.push(current_group); + } + + groups + } + + /// Generate cache key for a step. + fn cache_key(&self, step: &ChainStep, context: &ChainContext) -> String { + format!("{}:{}", step.instruction, context.current_url) + } + + /// Get cached result. + async fn get_cached(&self, key: &str) -> Option { + self.cache.get(key) + } + + /// Set cached result. + async fn set_cached(&self, key: &str, result: ChainStepResult) { + self.cache.set(key, result); + } + + /// Check if execution should continue after failure. + fn should_continue(&self, result: &ChainResult) -> bool { + // Continue if recent steps allowed failure + result + .step_results + .last() + .map(|r| !r.executed || r.success) + .unwrap_or(true) + } + + /// Clear the response cache. + pub async fn clear_cache(&self) { + self.cache.clear(); + } +} + +/// LRU cache for responses with TTL. +#[derive(Debug)] +pub struct ResponseCache { + entries: DashMap, + max_entries: usize, + ttl: Duration, +} + +#[derive(Debug, Clone)] +struct CacheEntry { + result: ChainStepResult, + created_at: Instant, +} + +impl Default for ResponseCache { + fn default() -> Self { + Self::new() + } +} + +impl ResponseCache { + /// Create a new cache. + pub fn new() -> Self { + Self { + entries: DashMap::new(), + max_entries: 1000, + ttl: Duration::from_secs(300), // 5 minutes + } + } + + /// Create with custom settings. + pub fn with_settings(max_entries: usize, ttl: Duration) -> Self { + Self { + entries: DashMap::with_capacity(max_entries), + max_entries, + ttl, + } + } + + /// Get a cached entry if valid. + pub fn get(&self, key: &str) -> Option { + if let Some(entry) = self.entries.get(key) { + if entry.created_at.elapsed() < self.ttl { + return Some(entry.result.clone()); + } + drop(entry); + self.entries.remove(key); + } + None + } + + /// Set a cache entry. + pub fn set(&self, key: &str, result: ChainStepResult) { + // Evict old entries if at capacity + if self.entries.len() >= self.max_entries { + self.evict_expired(); + if self.entries.len() >= self.max_entries { + self.evict_oldest(); + } + } + + self.entries.insert( + key.to_string(), + CacheEntry { + result, + created_at: Instant::now(), + }, + ); + } + + /// Clear the cache. + pub fn clear(&self) { + self.entries.clear(); + } + + /// Evict expired entries. + fn evict_expired(&self) { + let now = Instant::now(); + let expired: Vec = self + .entries + .iter() + .filter(|entry| now.duration_since(entry.value().created_at) >= self.ttl) + .map(|entry| entry.key().clone()) + .collect(); + + for key in expired { + self.entries.remove(&key); + } + } + + /// Evict oldest entry. + fn evict_oldest(&self) { + if let Some(oldest_key) = self + .entries + .iter() + .min_by_key(|entry| entry.value().created_at) + .map(|entry| entry.key().clone()) + { + self.entries.remove(&oldest_key); + } + } +} + +/// Batch executor for processing multiple items efficiently. +#[derive(Debug)] +pub struct BatchExecutor { + /// Maximum batch size. + pub max_batch_size: usize, + /// Maximum concurrent batches. + pub max_concurrent: usize, + /// Semaphore for concurrency control. + semaphore: Arc, +} + +impl Default for BatchExecutor { + fn default() -> Self { + Self::new() + } +} + +impl BatchExecutor { + /// Create a new batch executor. + pub fn new() -> Self { + Self { + max_batch_size: 10, + max_concurrent: 3, + semaphore: Arc::new(Semaphore::new(3)), + } + } + + /// Create with custom settings. + pub fn with_settings(max_batch_size: usize, max_concurrent: usize) -> Self { + Self { + max_batch_size, + max_concurrent, + semaphore: Arc::new(Semaphore::new(max_concurrent)), + } + } + + /// Process items in batches. + pub async fn process(&self, items: Vec, processor: F) -> Vec + where + T: Clone + Send + 'static, + R: Send + 'static, + F: Fn(T) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send, + { + let mut results = Vec::with_capacity(items.len()); + let chunks: Vec> = items + .into_iter() + .collect::>() + .chunks(self.max_batch_size) + .map(|c| c.to_vec()) + .collect(); + + for chunk in chunks { + let mut handles = Vec::with_capacity(chunk.len()); + + for item in chunk { + let semaphore = self.semaphore.clone(); + let processor = processor.clone(); + + handles.push(tokio::spawn(async move { + let _permit = match semaphore.acquire().await { + Ok(p) => Some(p), + Err(_) => { + log::warn!("Semaphore closed, proceeding without permit"); + None + } + }; + processor(item).await + })); + } + + for handle in handles { + if let Ok(result) = handle.await { + results.push(result); + } + } + } + + results + } + + /// Process items in parallel with index tracking. + pub async fn process_indexed( + &self, + items: Vec, + processor: F, + ) -> Vec<(usize, R)> + where + T: Clone + Send + 'static, + R: Send + 'static, + F: Fn(usize, T) -> Fut + Clone + Send + Sync + 'static, + Fut: std::future::Future + Send, + { + let mut results = Vec::with_capacity(items.len()); + let indexed: Vec<(usize, T)> = items.into_iter().enumerate().collect(); + + let chunks: Vec> = indexed + .into_iter() + .collect::>() + .chunks(self.max_batch_size) + .map(|c| c.to_vec()) + .collect(); + + for chunk in chunks { + let mut handles = Vec::with_capacity(chunk.len()); + + for (idx, item) in chunk { + let semaphore = self.semaphore.clone(); + let processor = processor.clone(); + + handles.push(tokio::spawn(async move { + let _permit = match semaphore.acquire().await { + Ok(p) => Some(p), + Err(_) => { + log::warn!("Semaphore closed, proceeding without permit"); + None + } + }; + (idx, processor(idx, item).await) + })); + } + + for handle in handles { + if let Ok(result) = handle.await { + results.push(result); + } + } + } + + // Sort by index + results.sort_by_key(|(idx, _)| *idx); + results + } +} + +/// Prefetch manager for predictive page loading. +#[derive(Debug)] +pub struct PrefetchManager { + /// URLs currently being prefetched. + in_progress: Arc>>>, + /// Prefetched content cache. + cache: Arc>, + /// Maximum prefetch cache size. + max_cache_size: usize, + /// Maximum concurrent prefetches. + max_concurrent: usize, + /// Semaphore for concurrency. + semaphore: Arc, +} + +#[derive(Debug, Clone)] +struct PrefetchedContent { + html: String, + fetched_at: Instant, +} + +impl Default for PrefetchManager { + fn default() -> Self { + Self::new() + } +} + +impl PrefetchManager { + /// Create a new prefetch manager. + pub fn new() -> Self { + Self { + in_progress: Arc::new(DashMap::new()), + cache: Arc::new(DashMap::new()), + max_cache_size: 50, + max_concurrent: 3, + semaphore: Arc::new(Semaphore::new(3)), + } + } + + /// Start prefetching a URL. + pub async fn prefetch(&self, url: String, fetcher: F) + where + F: FnOnce(String) -> Fut + Send + 'static, + Fut: std::future::Future> + Send, + { + // Fast path: return if a fresh prefetched entry is already available. + if let Some(content) = self.cache.get(&url) { + if content.fetched_at.elapsed() < Duration::from_secs(60) { + return; + } + drop(content); + self.cache.remove(&url); + } + + // Avoid duplicate in-flight prefetches for the same URL. + if let Entry::Occupied(_) = self.in_progress.entry(url.clone()) { + return; + } + + let semaphore = self.semaphore.clone(); + let cache = self.cache.clone(); + let url_clone = url.clone(); + let max_cache_size = self.max_cache_size; + + let handle = tokio::spawn(async move { + let _permit = match semaphore.acquire().await { + Ok(p) => Some(p), + Err(_) => { + log::warn!("Semaphore closed, proceeding without permit"); + None + } + }; + let result = fetcher(url_clone.clone()).await; + + if let Some(ref html) = result { + if max_cache_size > 0 { + // Evict oldest while at capacity. + while cache.len() >= max_cache_size { + if let Some(oldest_key) = cache + .iter() + .min_by_key(|entry| entry.value().fetched_at) + .map(|entry| entry.key().clone()) + { + cache.remove(&oldest_key); + } else { + break; + } + } + } + + cache.insert( + url_clone, + PrefetchedContent { + html: html.clone(), + fetched_at: Instant::now(), + }, + ); + } + + result + }); + + match self.in_progress.entry(url) { + Entry::Vacant(vacant) => { + vacant.insert(handle); + } + Entry::Occupied(_) => { + handle.abort(); + } + } + } + + /// Get prefetched content if available. + pub async fn get(&self, url: &str) -> Option { + // Check cache first + if let Some(content) = self.cache.get(url) { + if content.fetched_at.elapsed() < Duration::from_secs(60) { + return Some(content.html.clone()); + } + drop(content); + self.cache.remove(url); + } + + // Check if prefetch is in progress + let handle = self.in_progress.remove(url).map(|(_, handle)| handle); + + if let Some(handle) = handle { + // Wait for prefetch to complete + if let Ok(result) = handle.await { + return result; + } + } + + None + } + + /// Prefetch multiple URLs. + pub async fn prefetch_many(&self, urls: Vec, fetcher: F) + where + F: Fn(String) -> Fut + Clone + Send + 'static, + Fut: std::future::Future> + Send, + { + for url in urls { + let fetcher = fetcher.clone(); + self.prefetch(url, fetcher).await; + } + } + + /// Clear the prefetch cache. + pub async fn clear(&self) { + self.cache.clear(); + + let keys: Vec = self + .in_progress + .iter() + .map(|entry| entry.key().clone()) + .collect(); + for key in keys { + if let Some((_, handle)) = self.in_progress.remove(&key) { + handle.abort(); + } + } + } + + /// Get the maximum concurrent prefetches. + pub fn max_concurrent(&self) -> usize { + self.max_concurrent + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + #[tokio::test] + async fn test_batch_executor() { + let executor = BatchExecutor::with_settings(3, 2); + + let items: Vec = (0..10).collect(); + let results = executor.process(items, |x| async move { x * 2 }).await; + + assert_eq!(results.len(), 10); + } + + #[tokio::test] + async fn test_batch_executor_indexed() { + let executor = BatchExecutor::new(); + + let items = vec!["a", "b", "c"]; + let results = executor + .process_indexed(items, |idx, s| async move { format!("{}:{}", idx, s) }) + .await; + + assert_eq!(results.len(), 3); + assert_eq!(results[0], (0, "0:a".to_string())); + assert_eq!(results[1], (1, "1:b".to_string())); + assert_eq!(results[2], (2, "2:c".to_string())); + } + + #[tokio::test] + async fn test_response_cache() { + let cache = ResponseCache::new(); + + let result = ChainStepResult::executed(0, "test", true); + cache.set("key1", result.clone()); + + assert!(cache.get("key1").is_some()); + assert!(cache.get("key2").is_none()); + } + + #[test] + fn test_chain_executor_dependency_analysis() { + let executor = ChainExecutor::new(); + let context = ChainContext::new("https://example.com"); + + let steps = vec![ + ChainStep::new("Step 1"), + ChainStep::new("Step 2"), + ChainStep::new("Step 3").when(ChainCondition::PreviousSucceeded), + ChainStep::new("Step 4"), + ]; + + let groups = executor.analyze_dependencies(&steps, &context); + + // Should create groups based on dependencies + assert!(!groups.is_empty()); + } + + #[tokio::test] + async fn test_prefetch_manager_single_in_flight_for_same_url() { + let manager = Arc::new(PrefetchManager::new()); + let url = "https://example.com/page".to_string(); + let calls = Arc::new(AtomicUsize::new(0)); + + let mut tasks = tokio::task::JoinSet::new(); + for _ in 0..32usize { + let manager = manager.clone(); + let calls = calls.clone(); + let url = url.clone(); + tasks.spawn(async move { + manager + .prefetch(url, move |_| { + let calls = calls.clone(); + async move { + calls.fetch_add(1, Ordering::Relaxed); + tokio::time::sleep(Duration::from_millis(20)).await; + Some("ok".to_string()) + } + }) + .await; + }); + } + + while let Some(joined) = tasks.join_next().await { + assert!(joined.is_ok(), "prefetch task should not panic"); + } + + let html = manager.get("https://example.com/page").await; + assert!(html.is_some()); + assert_eq!(calls.load(Ordering::Relaxed), 1); + } + + #[tokio::test] + async fn benchmark_response_cache_concurrent_throughput() { + let cache = Arc::new(ResponseCache::with_settings( + 16_384, + Duration::from_secs(120), + )); + let workers = 48usize; + let iters_per_worker = 1_000usize; + let started = Instant::now(); + + let mut tasks = tokio::task::JoinSet::new(); + for worker in 0..workers { + let cache = cache.clone(); + tasks.spawn(async move { + let mut local_hits = 0usize; + for i in 0..iters_per_worker { + let key = format!("w{worker}:k{}", i % 256); + let res = ChainStepResult::executed(i, "bench", true); + cache.set(&key, res); + if cache.get(&key).is_some() { + local_hits += 1; + } + } + local_hits + }); + } + + let mut total_hits = 0usize; + while let Some(joined) = tasks.join_next().await { + total_hits += joined.expect("cache benchmark task should not panic"); + } + + let elapsed = started.elapsed(); + let total_ops = workers * iters_per_worker * 2; + eprintln!( + "response_cache benchmark: ops={} elapsed_ms={} approx_ops_per_sec={:.0}", + total_ops, + elapsed.as_millis(), + total_ops as f64 / elapsed.as_secs_f64().max(0.000_001) + ); + + assert_eq!(total_hits, workers * iters_per_worker); + assert!( + elapsed < Duration::from_secs(10), + "cache benchmark took unexpectedly long: {}ms", + elapsed.as_millis() + ); + } + + // ==================================================================== + // Hardening: semaphore closed graceful degradation + // ==================================================================== + + /// When a semaphore is closed (e.g. executor dropped), the hardened + /// match pattern must not panic — it proceeds without a permit. + #[tokio::test] + async fn test_semaphore_closed_proceeds_without_permit() { + let sem = Arc::new(Semaphore::new(1)); + sem.close(); + + let permit = match sem.acquire().await { + Ok(p) => Some(p), + Err(_) => None, + }; + assert!( + permit.is_none(), + "closed semaphore must yield None, not panic" + ); + } + + /// Normal open semaphore still yields a valid permit. + #[tokio::test] + async fn test_semaphore_open_returns_valid_permit() { + let sem = Arc::new(Semaphore::new(1)); + let permit = match sem.acquire().await { + Ok(p) => Some(p), + Err(_) => None, + }; + assert!(permit.is_some(), "open semaphore must yield a permit"); + } + + /// The real executor must complete steps even when its semaphore + /// has been closed — verifies the hardened pattern end-to-end. + #[tokio::test] + async fn test_executor_completes_chain_after_semaphore_closed() { + let executor = ChainExecutor::new(); + executor.semaphore.close(); + + let steps = vec![ChainStep::new("resilient step")]; + let context = ChainContext::new("https://example.com"); + let result = executor + .execute(steps, context, |s, _ctx| async move { + ChainStepResult::executed(0, &s.instruction, true) + }) + .await; + + assert!( + !result.step_results.is_empty(), + "chain must produce results despite closed semaphore" + ); + assert!( + result.step_results[0].success, + "step must succeed despite closed semaphore" + ); + } + + /// Multiple concurrent spawned tasks must all proceed when the + /// semaphore is closed — none should hang or panic. + #[tokio::test] + async fn test_concurrent_tasks_proceed_with_closed_semaphore() { + let sem = Arc::new(Semaphore::new(5)); + sem.close(); + + let mut set = tokio::task::JoinSet::new(); + for i in 0..10usize { + let sem = sem.clone(); + set.spawn(async move { + let _permit = match sem.acquire().await { + Ok(p) => Some(p), + Err(_) => None, + }; + i * 2 // actual work + }); + } + + let mut results = Vec::new(); + while let Some(joined) = set.join_next().await { + results.push(joined.expect("task must not panic")); + } + results.sort(); + assert_eq!(results, vec![0, 2, 4, 6, 8, 10, 12, 14, 16, 18]); + } +} diff --git a/spider_agent/src/automation/helpers.rs b/spider_agent/src/automation/helpers.rs new file mode 100644 index 000000000..24c6e83e7 --- /dev/null +++ b/spider_agent/src/automation/helpers.rs @@ -0,0 +1,381 @@ +//! Helper functions for JSON parsing that depend on [`EngineError`]. +//! +//! Contains [`best_effort_parse_json_object`] and supporting repair utilities +//! for robustly handling LLM output quirks. Pure parsing helpers (code-block +//! extraction, JSON boundary detection, truncation, hashing) live in +//! [`spider_agent_types`] and are re-exported at the parent module level. + +use super::{ + extract_last_code_block, extract_last_json_array, extract_last_json_object, EngineError, + EngineResult, +}; +use serde_json::Value; + +/// Best effort parse the JSON object. +/// +/// Handles common LLM output quirks: +/// - Multiple ```json``` blocks (uses the LAST one, as thinking models refine answers) +/// - Reasoning/thinking text before JSON +/// - Nested JSON structures (proper brace matching) +/// - Duplicate closing braces from JS code inside JSON strings +/// - Unquoted bare-word values from small models +pub fn best_effort_parse_json_object(s: &str) -> EngineResult { + // Try direct parse first + if let Ok(v) = serde_json::from_str::(s) { + return Ok(v); + } + + let trimmed = s.trim(); + + // 0. Repair common LLM JSON errors: duplicate closing braces after strings in arrays. + // LLMs often output `"}},` instead of `"},` when JS code with braces is inside a JSON string. + // This happens because the model confuses JS braces with JSON structure braces. + let repaired = repair_json_braces(trimmed); + if let Ok(v) = serde_json::from_str::(&repaired) { + return Ok(v); + } + + // 1. Try to extract the LAST code block (thinking models refine their answer) + if let Some(block) = extract_last_code_block(trimmed) { + if let Ok(v) = serde_json::from_str::(block) { + return Ok(v); + } + + // Try repair on the code block too + let repaired_block = repair_json_braces(block); + if let Ok(v) = serde_json::from_str::(&repaired_block) { + return Ok(v); + } + + // The code block might have prose - try extracting JSON from within it + if let Some(obj) = extract_last_json_object(block) { + if let Ok(v) = serde_json::from_str::(obj) { + return Ok(v); + } + } + } + + // 2. Strip markdown fences if at boundaries + let unfenced = trimmed + .strip_prefix("```json") + .or_else(|| trimmed.strip_prefix("```")) + .map(|x| x.trim()) + .unwrap_or(trimmed); + let unfenced = unfenced + .strip_suffix("```") + .map(|x| x.trim()) + .unwrap_or(unfenced); + + if let Ok(v) = serde_json::from_str::(unfenced) { + return Ok(v); + } + + // Try repair on unfenced content + let repaired_unfenced = repair_json_braces(unfenced); + if let Ok(v) = serde_json::from_str::(&repaired_unfenced) { + return Ok(v); + } + + // 3. Extract last JSON object with proper brace matching + if let Some(obj) = extract_last_json_object(unfenced) { + if let Ok(v) = serde_json::from_str::(obj) { + return Ok(v); + } + } + + // 4. Extract last JSON array with proper bracket matching + if let Some(arr) = extract_last_json_array(unfenced) { + if let Ok(v) = serde_json::from_str::(arr) { + return Ok(v); + } + } + + // 5. Repair unquoted bare-word values (common with small models like Gemini Nano) + // e.g. {"selector": a, "action": "click"} → {"selector": "a", "action": "click"} + let quoted = repair_unquoted_json_values(unfenced); + if let Ok(v) = serde_json::from_str::("ed) { + return Ok(v); + } + // Also try on extracted JSON objects + if let Some(obj) = extract_last_json_object("ed) { + if let Ok(v) = serde_json::from_str::(obj) { + return Ok(v); + } + } + + log::warn!( + "best_effort_parse_json_object failed on content (first 300 chars): {}", + &s[..s.len().min(300)] + ); + + Err(EngineError::InvalidField( + "assistant content was not a JSON object", + )) +} + +/// Repair unquoted bare-word values in JSON — common with small models. +/// +/// Converts `"key": bare_value,` → `"key": "bare_value",` +/// Only handles simple cases outside of nested structures. +fn repair_unquoted_json_values(s: &str) -> String { + let bytes = s.as_bytes(); + let len = bytes.len(); + let mut result = Vec::with_capacity(len + 64); + let mut i = 0; + let mut in_string = false; + let mut escape_next = false; + + while i < len { + let b = bytes[i]; + + if escape_next { + result.push(b); + escape_next = false; + i += 1; + continue; + } + + if b == b'\\' && in_string { + result.push(b); + escape_next = true; + i += 1; + continue; + } + + if b == b'"' { + in_string = !in_string; + result.push(b); + i += 1; + continue; + } + + if in_string { + result.push(b); + i += 1; + continue; + } + + // Outside a string — look for `:` followed by an unquoted bare word + if b == b':' { + result.push(b); + i += 1; + // Skip whitespace after colon + while i < len && bytes[i].is_ascii_whitespace() { + result.push(bytes[i]); + i += 1; + } + if i >= len { + break; + } + let c = bytes[i]; + // Valid JSON value starts: " [ { digit - true false null + if c == b'"' || c == b'[' || c == b'{' || c.is_ascii_digit() || c == b'-' { + continue; + } + let remaining = &s[i..]; + if remaining.starts_with("true") + || remaining.starts_with("false") + || remaining.starts_with("null") + { + continue; + } + // Bare word — collect until , } ] or newline + let word_start = i; + while i < len { + let ch = bytes[i]; + if ch == b',' || ch == b'}' || ch == b']' || ch == b'\n' || ch == b'\r' { + break; + } + i += 1; + } + let word = s[word_start..i].trim(); + if !word.is_empty() { + result.push(b'"'); + // Escape any quotes inside the bare word + for &wb in word.as_bytes() { + if wb == b'"' { + result.push(b'\\'); + } + result.push(wb); + } + result.push(b'"'); + } + continue; + } + + result.push(b); + i += 1; + } + + String::from_utf8(result).unwrap_or_else(|_| s.to_string()) +} + +/// Repair common JSON brace errors produced by LLMs. +/// +/// When LLMs embed JavaScript code (with curly braces) inside JSON strings, +/// they often add extra closing braces, producing patterns like `"}}` instead of `"}`. +/// This function attempts to fix these by removing duplicate braces that break JSON structure. +fn repair_json_braces(s: &str) -> String { + // Strategy: walk the string tracking JSON structure (respecting string boundaries). + // When we see `"}}"` followed by `,` or `]`, the inner `}}` likely has one extra `}`. + let bytes = s.as_bytes(); + let len = bytes.len(); + let mut result = Vec::with_capacity(len); + let mut i = 0; + let mut in_string = false; + let mut escape_next = false; + + while i < len { + let b = bytes[i]; + + if escape_next { + escape_next = false; + result.push(b); + i += 1; + continue; + } + + if b == b'\\' && in_string { + escape_next = true; + result.push(b); + i += 1; + continue; + } + + if b == b'"' { + in_string = !in_string; + result.push(b); + i += 1; + continue; + } + + if in_string { + result.push(b); + i += 1; + continue; + } + + // Outside a string: look for `}}` patterns that are likely errors. + // Pattern: `}` followed by `}` then `,` or `]` or `}` + // If the original string doesn't parse but removing one `}` would fix it, + // skip one `}`. + if b == b'}' && i + 1 < len && bytes[i + 1] == b'}' { + // Look ahead past the `}}` for `,` or `]` + let after = if i + 2 < len { bytes[i + 2] } else { 0 }; + if after == b',' || after == b']' || after == b'}' { + // Skip one `}` - keep only one + result.push(b'}'); + i += 2; // skip both `}`, but only emit one + continue; + } + } + + result.push(b); + i += 1; + } + + String::from_utf8(result).unwrap_or_else(|_| s.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_best_effort_parse_simple() { + let result = best_effort_parse_json_object("{\"key\": \"value\"}"); + assert!(result.is_ok()); + assert_eq!(result.unwrap()["key"], "value"); + } + + #[test] + fn test_best_effort_parse_with_fences() { + let result = best_effort_parse_json_object("```json\n{\"key\": \"value\"}\n```"); + assert!(result.is_ok()); + assert_eq!(result.unwrap()["key"], "value"); + } + + #[test] + fn test_best_effort_parse_with_prose() { + let result = best_effort_parse_json_object( + "Here's my thinking...\n\nThe answer is: {\"key\": \"value\"}", + ); + assert!(result.is_ok()); + assert_eq!(result.unwrap()["key"], "value"); + } + + #[test] + fn test_repair_json_braces_evaluate() { + // LLM outputs `}}` instead of `}` after Evaluate strings containing JS code with braces + let broken = r#"{"label":"test","done":false,"steps":[{"Evaluate":"document.title = JSON.stringify({a:1});"}},{"Wait":300}],"extracted":{"level":7}}"#; + let result = best_effort_parse_json_object(broken); + assert!( + result.is_ok(), + "Should repair double-brace error: {:?}", + result.err() + ); + let val = result.unwrap(); + assert_eq!(val["label"], "test"); + let steps = val["steps"].as_array().unwrap(); + assert_eq!(steps.len(), 2); + assert!(steps[0].get("Evaluate").is_some()); + assert!(steps[1].get("Wait").is_some()); + } + + #[test] + fn test_repair_json_braces_valid_not_broken() { + // Valid JSON with nested objects should NOT be broken by repair + let valid = r#"{"a":{"b":{"c":1}},"d":[1,2]}"#; + let result = best_effort_parse_json_object(valid); + assert!(result.is_ok()); + let val = result.unwrap(); + assert_eq!(val["a"]["b"]["c"], 1); + } + + #[test] + fn test_repair_unquoted_json_values_bare_word() { + // Common small model output: "selector": a instead of "selector": "a" + let input = r#"{"label": "Click the checkbox", "selector": a, "action": "click"}"#; + let repaired = repair_unquoted_json_values(input); + let parsed: serde_json::Value = serde_json::from_str(&repaired).unwrap(); + assert_eq!(parsed["selector"], "a"); + assert_eq!(parsed["action"], "click"); + assert_eq!(parsed["label"], "Click the checkbox"); + } + + #[test] + fn test_repair_unquoted_json_values_multiple() { + let input = r#"{"a": hello, "b": world, "c": 42}"#; + let repaired = repair_unquoted_json_values(input); + let parsed: serde_json::Value = serde_json::from_str(&repaired).unwrap(); + assert_eq!(parsed["a"], "hello"); + assert_eq!(parsed["b"], "world"); + assert_eq!(parsed["c"], 42); + } + + #[test] + fn test_repair_unquoted_json_values_preserves_valid() { + let input = r#"{"a": "quoted", "b": 123, "c": true, "d": null, "e": [1]}"#; + let repaired = repair_unquoted_json_values(input); + let parsed: serde_json::Value = serde_json::from_str(&repaired).unwrap(); + assert_eq!(parsed["a"], "quoted"); + assert_eq!(parsed["b"], 123); + assert_eq!(parsed["c"], true); + assert!(parsed["d"].is_null()); + } + + #[test] + fn test_best_effort_parse_unquoted_values() { + // End-to-end: markdown-fenced response with unquoted bare word + let input = "```json\n{\"label\": \"Click the checkbox\", \"selector\": a, \"action\": \"click\"}\n```"; + let result = best_effort_parse_json_object(input); + assert!( + result.is_ok(), + "Should parse unquoted bare word: {:?}", + result.err() + ); + let val = result.unwrap(); + assert_eq!(val["selector"], "a"); + assert_eq!(val["action"], "click"); + } +} diff --git a/spider_agent/src/automation/long_term_memory.rs b/spider_agent/src/automation/long_term_memory.rs new file mode 100644 index 000000000..5ec93eb29 --- /dev/null +++ b/spider_agent/src/automation/long_term_memory.rs @@ -0,0 +1,778 @@ +//! Long-term experience memory for agent learning. +//! +//! Stores successful automation experiences in an append-only `.jsonl` log +//! and uses `memvid-rs` for semantic search over past strategies. +//! The memvid video+index is rebuilt lazily when recall is needed after +//! new experiences have been added. + +use dashmap::DashMap; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// Outcome of an automation experience. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExperienceOutcome { + /// Automation completed successfully. + Success, + /// Automation failed. + Failure, +} + +impl std::fmt::Display for ExperienceOutcome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Failure => write!(f, "failure"), + } + } +} + +/// A single recorded automation experience. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExperienceRecord { + /// Type of challenge encountered (e.g. "image-grid-selection"). + pub challenge_type: String, + /// Domain/path pattern of the URL. + pub url_pattern: String, + /// Keywords extracted from the page title. + pub title_keywords: Vec, + /// CSS classes/elements that identify the challenge. + pub html_signals: Vec, + /// Human-readable summary of what strategy worked. + pub strategy_summary: String, + /// Serialized action steps as JSON. + pub steps_json: String, + /// Whether the automation succeeded or failed. + pub outcome: ExperienceOutcome, + /// Number of rounds the automation took. + pub rounds_taken: u32, + /// Unix timestamp of when the experience was recorded. + pub timestamp: u64, +} + +impl ExperienceRecord { + /// Build an experience record from a completed automation session. + /// + /// Extracts URL pattern, title keywords, and constructs a strategy summary + /// from the session data. + pub fn from_session( + url: &str, + label: &str, + memory: &super::AutomationMemory, + steps_executed: usize, + rounds: u32, + ) -> Self { + // Extract domain pattern from URL + let url_pattern = url.split('/').take(3).collect::>().join("/") + "/*"; + + // Extract title keywords (non-trivial words) + let title_keywords: Vec = label + .split_whitespace() + .filter(|w| w.len() > 2) + .map(|w| w.to_lowercase()) + .collect(); + + // Build strategy summary from memory actions + let actions: Vec = memory + .action_history + .iter() + .rev() + .take(10) + .rev() + .cloned() + .collect(); + let strategy_summary = if actions.is_empty() { + format!("Completed '{}' in {} steps", label, steps_executed) + } else { + let action_summary = actions.join(" → "); + if action_summary.len() > 300 { + format!("{}...", &action_summary[..297]) + } else { + action_summary + } + }; + + let steps_json = serde_json::to_string(&actions).unwrap_or_default(); + + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + Self { + challenge_type: label.to_string(), + url_pattern, + title_keywords, + html_signals: Vec::new(), + strategy_summary, + steps_json, + outcome: ExperienceOutcome::Success, + rounds_taken: rounds, + timestamp, + } + } + + /// Convert this record to a searchable text representation for memvid indexing. + fn to_search_text(&self) -> String { + format!( + "challenge: {} url: {} keywords: {} strategy: {} outcome: {} rounds: {}", + self.challenge_type, + self.url_pattern, + self.title_keywords.join(", "), + self.strategy_summary, + self.outcome, + self.rounds_taken, + ) + } +} + +/// A recalled experience with its relevance score. +#[derive(Debug, Clone)] +pub struct RecalledExperience { + /// The original experience record. + pub record: ExperienceRecord, + /// Relevance score from semantic search (0.0 - 1.0). + pub relevance: f32, +} + +/// Configuration for the experience memory system. +#[derive(Debug, Clone)] +pub struct ExperienceMemoryConfig { + /// Maximum number of experiences to retrieve per query. + pub max_recall: usize, + /// Maximum total characters of context to inject into prompts. + pub max_context_chars: usize, + /// Minimum relevance score threshold for recall results. + pub min_relevance_score: f32, + /// Text chunk size for memvid indexing. + pub chunk_size: usize, + /// Chunk overlap for memvid indexing. + pub overlap: usize, +} + +impl Default for ExperienceMemoryConfig { + fn default() -> Self { + Self { + max_recall: 3, + max_context_chars: 2000, + min_relevance_score: 0.15, + chunk_size: 512, + overlap: 32, + } + } +} + +/// Statistics about the experience memory. +#[derive(Debug, Clone, Default)] +pub struct MemoryStats { + /// Total number of stored experiences. + pub experience_count: usize, + /// Size of the .jsonl log file in bytes. + pub log_size_bytes: u64, + /// Size of the memvid video file in bytes. + pub video_size_bytes: u64, + /// Size of the memvid index file in bytes. + pub index_size_bytes: u64, + /// Whether the index needs rebuilding. + pub dirty: bool, +} + +/// Manages long-term experience storage and semantic retrieval. +/// +/// Experiences are stored in an append-only `.jsonl` file (source of truth). +/// A memvid video+index is rebuilt lazily when recall is needed after new +/// experiences have been added. This avoids the cost of rebuilding on every +/// store operation. +/// +/// Not `Clone` because it owns the memvid retriever handle. +pub struct ExperienceMemory { + /// Path to the .jsonl append-only experience log. + log_path: PathBuf, + /// Path to the memvid .mp4 video file. + video_path: PathBuf, + /// Path to the memvid .db index file. + index_path: PathBuf, + /// Lazy-loaded memvid retriever. + retriever: Option, + /// True when .jsonl has new entries since last index build. + dirty: bool, + /// Cache of query hash → recalled experiences. + recall_cache: DashMap>, + /// Configuration for this memory instance. + pub config: ExperienceMemoryConfig, +} + +// SAFETY: ExperienceMemory is always accessed through Arc>, +// which provides proper synchronization. The non-Send/Sync interiors (rusqlite's +// RefCell and ffmpeg raw pointers inside MemvidRetriever) are never accessed +// concurrently — the RwLock guards all access. +unsafe impl Send for ExperienceMemory {} +unsafe impl Sync for ExperienceMemory {} + +impl std::fmt::Debug for ExperienceMemory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ExperienceMemory") + .field("log_path", &self.log_path) + .field("video_path", &self.video_path) + .field("index_path", &self.index_path) + .field("has_retriever", &self.retriever.is_some()) + .field("dirty", &self.dirty) + .field("cache_entries", &self.recall_cache.len()) + .field("config", &self.config) + .finish() + } +} + +impl ExperienceMemory { + /// Create or load an experience memory from a directory. + /// + /// If the directory doesn't exist, it will be created. + /// If existing `.jsonl` / `.mp4` / `.db` files are found, they are loaded. + pub async fn new( + dir: impl Into, + config: ExperienceMemoryConfig, + ) -> std::io::Result { + let dir = dir.into(); + tokio::fs::create_dir_all(&dir).await?; + + let log_path = dir.join("experiences.jsonl"); + let video_path = dir.join("experiences.mp4"); + let index_path = dir.join("experiences.db"); + + // Determine if we have unindexed experiences + let log_exists = log_path.exists(); + let index_exists = video_path.exists() && index_path.exists(); + let dirty = log_exists && !index_exists; + + Ok(Self { + log_path, + video_path, + index_path, + retriever: None, + dirty, + recall_cache: DashMap::new(), + config, + }) + } + + /// Append an experience record to the .jsonl log. + /// + /// Marks the index as dirty so it will be rebuilt on next recall. + pub async fn store_experience(&mut self, record: &ExperienceRecord) -> std::io::Result<()> { + use tokio::io::AsyncWriteExt; + + let mut line = serde_json::to_string(record) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + line.push('\n'); + + let mut file = tokio::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&self.log_path) + .await?; + file.write_all(line.as_bytes()).await?; + file.flush().await?; + + self.dirty = true; + self.recall_cache.clear(); + self.retriever = None; + + log::debug!( + "Stored experience: challenge={} outcome={} rounds={}", + record.challenge_type, + record.outcome, + record.rounds_taken, + ); + + Ok(()) + } + + /// Recall relevant past experiences for a given query. + /// + /// If the index is dirty (new experiences added since last build), + /// it will be rebuilt first. Results are cached by query hash. + pub async fn recall( + &mut self, + query: &str, + k: usize, + ) -> Result, Box> { + let query_hash = super::fnv1a64(query.as_bytes()); + + // Check cache first + if let Some(cached) = self.recall_cache.get(&query_hash) { + return Ok(cached.clone()); + } + + // Rebuild index if dirty + if self.dirty { + self.rebuild_index().await?; + } + + // Load retriever if needed + if self.retriever.is_none() { + if !self.video_path.exists() || !self.index_path.exists() { + return Ok(Vec::new()); + } + self.retriever = Some( + memvid_rs::MemvidRetriever::new( + self.video_path.to_string_lossy().as_ref(), + self.index_path.to_string_lossy().as_ref(), + ) + .await?, + ); + } + + let retriever = self.retriever.as_mut().expect("retriever loaded"); + let search_results = retriever.search(query, k).await?; + + // Load all records for matching + let all_records = self.load_all_records().await?; + + let mut experiences = Vec::new(); + for (score, text) in &search_results { + // Match search result text back to an experience record + let relevance = *score; + if relevance < self.config.min_relevance_score { + continue; + } + + // Find the best matching record for this search result + if let Some(record) = Self::match_record_to_search_result(&all_records, text) { + experiences.push(RecalledExperience { record, relevance }); + } + } + + // Cache results + self.recall_cache.insert(query_hash, experiences.clone()); + + Ok(experiences) + } + + /// Format recalled experiences as context suitable for prompt injection. + /// + /// Output is capped at `max_context_chars` to keep prompts manageable. + pub fn recall_to_context( + experiences: &[RecalledExperience], + max_context_chars: usize, + ) -> String { + if experiences.is_empty() { + return String::new(); + } + + let mut ctx = String::from("## Learned Strategies (from past experience)\n"); + let mut remaining = max_context_chars.saturating_sub(ctx.len()); + + for exp in experiences { + let mut entry = format!( + "### {} (relevance: {:.2})\n", + exp.record.challenge_type, exp.relevance, + ); + entry.push_str(&format!("URL pattern: {}\n", exp.record.url_pattern)); + + // Truncate strategy summary if needed + let summary = if exp.record.strategy_summary.len() > 300 { + format!("{}...", &exp.record.strategy_summary[..297]) + } else { + exp.record.strategy_summary.clone() + }; + entry.push_str(&format!("Strategy: {}\n", summary)); + + if exp.record.rounds_taken > 0 { + entry.push_str(&format!( + "Outcome: {} in {} rounds\n", + exp.record.outcome, exp.record.rounds_taken + )); + } + entry.push_str("---\n"); + + if entry.len() > remaining { + break; + } + remaining -= entry.len(); + ctx.push_str(&entry); + } + + ctx + } + + /// Remove experiences matching a semantic query from the log. + /// + /// Searches for the top-k matches and removes them from the `.jsonl` file. + /// Marks the index as dirty. + pub async fn release_by_query( + &mut self, + query: &str, + k: usize, + ) -> Result> { + let matches = self.recall(query, k).await?; + if matches.is_empty() { + return Ok(0); + } + + // Collect timestamps of records to remove + let remove_timestamps: std::collections::HashSet = + matches.iter().map(|m| m.record.timestamp).collect(); + + let all_records = self.load_all_records().await?; + let remaining: Vec<&ExperienceRecord> = all_records + .iter() + .filter(|r| !remove_timestamps.contains(&r.timestamp)) + .collect(); + + let removed_count = all_records.len() - remaining.len(); + + // Rewrite the log with remaining records + self.rewrite_log(&remaining).await?; + + self.dirty = true; + self.recall_cache.clear(); + self.retriever = None; + + Ok(removed_count) + } + + /// Delete all experience data and reset state. + pub async fn release_all(&mut self) -> std::io::Result<()> { + for path in [&self.log_path, &self.video_path, &self.index_path] { + if path.exists() { + let _ = tokio::fs::remove_file(path).await; + } + } + self.dirty = false; + self.recall_cache.clear(); + self.retriever = None; + Ok(()) + } + + /// Get statistics about the current memory state. + pub fn stats(&self) -> MemoryStats { + let log_size = std::fs::metadata(&self.log_path) + .map(|m| m.len()) + .unwrap_or(0); + let video_size = std::fs::metadata(&self.video_path) + .map(|m| m.len()) + .unwrap_or(0); + let index_size = std::fs::metadata(&self.index_path) + .map(|m| m.len()) + .unwrap_or(0); + + // Count lines in .jsonl for experience count + let count = std::fs::read_to_string(&self.log_path) + .map(|s| s.lines().filter(|l| !l.trim().is_empty()).count()) + .unwrap_or(0); + + MemoryStats { + experience_count: count, + log_size_bytes: log_size, + video_size_bytes: video_size, + index_size_bytes: index_size, + dirty: self.dirty, + } + } + + /// Force rebuild the memvid index from the .jsonl log. + pub async fn flush(&mut self) -> Result<(), Box> { + self.rebuild_index().await?; + Ok(()) + } + + /// Clear the recall cache. Called at the start of each new automation run. + pub fn clear_cache(&self) { + self.recall_cache.clear(); + } + + // ---- Internal helpers ---- + + /// Rebuild the memvid video+index from the .jsonl log. + async fn rebuild_index(&mut self) -> Result<(), Box> { + let records = self.load_all_records().await?; + if records.is_empty() { + self.dirty = false; + return Ok(()); + } + + // Build combined text from all records + let combined_text: String = records + .iter() + .map(|r| r.to_search_text()) + .collect::>() + .join("\n\n"); + + let video_path_str = self.video_path.to_string_lossy().to_string(); + let index_path_str = self.index_path.to_string_lossy().to_string(); + let chunk_size = self.config.chunk_size; + let overlap = self.config.overlap; + + // Encode and build index + let mut encoder = memvid_rs::MemvidEncoder::new(None).await?; + encoder + .add_text(&combined_text, chunk_size, overlap) + .await?; + encoder + .build_video(&video_path_str, &index_path_str) + .await?; + + self.dirty = false; + self.retriever = None; // will be reloaded on next recall + self.recall_cache.clear(); + + log::debug!( + "Rebuilt experience index: {} records, video={}, index={}", + records.len(), + self.video_path.display(), + self.index_path.display(), + ); + + Ok(()) + } + + /// Load all experience records from the .jsonl log. + async fn load_all_records(&self) -> std::io::Result> { + if !self.log_path.exists() { + return Ok(Vec::new()); + } + + let content = tokio::fs::read_to_string(&self.log_path).await?; + let records: Vec = content + .lines() + .filter(|line| !line.trim().is_empty()) + .filter_map(|line| serde_json::from_str(line).ok()) + .collect(); + + Ok(records) + } + + /// Rewrite the .jsonl log with only the given records. + async fn rewrite_log(&self, records: &[&ExperienceRecord]) -> std::io::Result<()> { + use tokio::io::AsyncWriteExt; + + let mut content = String::new(); + for record in records { + let line = serde_json::to_string(record) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + content.push_str(&line); + content.push('\n'); + } + + let mut file = tokio::fs::File::create(&self.log_path).await?; + file.write_all(content.as_bytes()).await?; + file.flush().await?; + + Ok(()) + } + + /// Match a search result text back to the most similar experience record. + fn match_record_to_search_result( + records: &[ExperienceRecord], + search_text: &str, + ) -> Option { + let search_lower = search_text.to_lowercase(); + records + .iter() + .max_by_key(|r| { + let record_text = r.to_search_text().to_lowercase(); + // Simple overlap score: count shared words + let record_words: std::collections::HashSet<&str> = + record_text.split_whitespace().collect(); + let search_words: std::collections::HashSet<&str> = + search_lower.split_whitespace().collect(); + record_words.intersection(&search_words).count() + }) + .cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_experience_outcome_display() { + assert_eq!(ExperienceOutcome::Success.to_string(), "success"); + assert_eq!(ExperienceOutcome::Failure.to_string(), "failure"); + } + + #[test] + fn test_experience_record_serialization() { + let record = ExperienceRecord { + challenge_type: "image-grid-selection".to_string(), + url_pattern: "https://example.com/*".to_string(), + title_keywords: vec!["select".to_string(), "images".to_string()], + html_signals: vec!["grid-item".to_string()], + strategy_summary: "Click matching grid tiles".to_string(), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 3, + timestamp: 1700000000, + }; + + let json = serde_json::to_string(&record).unwrap(); + let deserialized: ExperienceRecord = serde_json::from_str(&json).unwrap(); + + assert_eq!(deserialized.challenge_type, "image-grid-selection"); + assert_eq!(deserialized.outcome, ExperienceOutcome::Success); + assert_eq!(deserialized.rounds_taken, 3); + assert_eq!(deserialized.title_keywords.len(), 2); + } + + #[test] + fn test_config_defaults() { + let config = ExperienceMemoryConfig::default(); + assert_eq!(config.max_recall, 3); + assert_eq!(config.max_context_chars, 2000); + assert_eq!(config.min_relevance_score, 0.15); + assert_eq!(config.chunk_size, 512); + assert_eq!(config.overlap, 32); + } + + #[test] + fn test_recall_to_context_empty() { + let ctx = ExperienceMemory::recall_to_context(&[], 2000); + assert!(ctx.is_empty()); + } + + #[test] + fn test_recall_to_context_formatting() { + let experiences = vec![RecalledExperience { + record: ExperienceRecord { + challenge_type: "grid-selection".to_string(), + url_pattern: "https://example.com/*".to_string(), + title_keywords: vec!["select".to_string()], + html_signals: vec![], + strategy_summary: "Click grid tiles matching description".to_string(), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 2, + timestamp: 1700000000, + }, + relevance: 0.82, + }]; + + let ctx = ExperienceMemory::recall_to_context(&experiences, 2000); + assert!(ctx.contains("Learned Strategies")); + assert!(ctx.contains("grid-selection")); + assert!(ctx.contains("0.82")); + assert!(ctx.contains("Click grid tiles matching description")); + assert!(ctx.contains("example.com")); + } + + #[test] + fn test_recall_to_context_respects_max_chars() { + let experiences: Vec = (0..100) + .map(|i| RecalledExperience { + record: ExperienceRecord { + challenge_type: format!("challenge-type-{}", i), + url_pattern: format!("https://example{}.com/*", i), + title_keywords: vec![], + html_signals: vec![], + strategy_summary: "A".repeat(200), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 1, + timestamp: 1700000000 + i as u64, + }, + relevance: 0.9, + }) + .collect(); + + let ctx = ExperienceMemory::recall_to_context(&experiences, 500); + assert!(ctx.len() <= 600); // some slack for the last entry that fits + } + + #[test] + fn test_memory_stats_default() { + let stats = MemoryStats::default(); + assert_eq!(stats.experience_count, 0); + assert_eq!(stats.log_size_bytes, 0); + assert!(!stats.dirty); + } + + #[test] + fn test_experience_record_to_search_text() { + let record = ExperienceRecord { + challenge_type: "image-grid".to_string(), + url_pattern: "https://example.com/*".to_string(), + title_keywords: vec!["select".to_string(), "all".to_string()], + html_signals: vec![], + strategy_summary: "Click matching tiles".to_string(), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 3, + timestamp: 0, + }; + + let text = record.to_search_text(); + assert!(text.contains("image-grid")); + assert!(text.contains("example.com")); + assert!(text.contains("select, all")); + assert!(text.contains("Click matching tiles")); + assert!(text.contains("success")); + } + + #[tokio::test] + async fn test_store_and_load() { + let dir = std::env::temp_dir().join(format!("spider_exp_test_{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + + let mut mem = ExperienceMemory::new(&dir, ExperienceMemoryConfig::default()) + .await + .unwrap(); + + let record = ExperienceRecord { + challenge_type: "test".to_string(), + url_pattern: "https://test.com/*".to_string(), + title_keywords: vec!["test".to_string()], + html_signals: vec![], + strategy_summary: "Test strategy".to_string(), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 1, + timestamp: 1700000000, + }; + + mem.store_experience(&record).await.unwrap(); + + let stats = mem.stats(); + assert_eq!(stats.experience_count, 1); + assert!(stats.dirty); + assert!(stats.log_size_bytes > 0); + + // Cleanup + let _ = std::fs::remove_dir_all(&dir); + } + + #[tokio::test] + async fn test_release_all() { + let dir = std::env::temp_dir().join(format!("spider_exp_release_{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + + let mut mem = ExperienceMemory::new(&dir, ExperienceMemoryConfig::default()) + .await + .unwrap(); + + let record = ExperienceRecord { + challenge_type: "test".to_string(), + url_pattern: "https://test.com/*".to_string(), + title_keywords: vec![], + html_signals: vec![], + strategy_summary: "Test".to_string(), + steps_json: "[]".to_string(), + outcome: ExperienceOutcome::Success, + rounds_taken: 1, + timestamp: 1700000000, + }; + + mem.store_experience(&record).await.unwrap(); + assert!(mem.log_path.exists()); + + mem.release_all().await.unwrap(); + assert!(!mem.log_path.exists()); + + let stats = mem.stats(); + assert_eq!(stats.experience_count, 0); + assert!(!stats.dirty); + + // Cleanup + let _ = std::fs::remove_dir_all(&dir); + } +} diff --git a/spider_agent/src/automation/mod.rs b/spider_agent/src/automation/mod.rs new file mode 100644 index 000000000..7c0427458 --- /dev/null +++ b/spider_agent/src/automation/mod.rs @@ -0,0 +1,236 @@ +//! Automation module for spider_agent. +//! +//! Provides sophisticated automation capabilities including: +//! - Action chains with conditional execution +//! - Self-healing selector cache +//! - Page observation and understanding +//! - Recovery strategies for resilient automation +//! - Content analysis for smart decisions +//! - Remote multimodal engine configuration +//! - HTML cleaning utilities +//! - Memory operations for session state +//! +//! ## Crate Structure +//! +//! Core types and constants are defined in [`spider_agent_types`] and re-exported here +//! for backward compatibility. HTML cleaning utilities come from [`spider_agent_html`]. +//! Runtime components (engine, browser, executor, router) remain in this crate. + +// ========================================================================= +// Local modules (runtime components that stay in spider_agent) +// ========================================================================= + +#[cfg(feature = "chrome")] +mod browser; +pub mod cache; +mod concurrent_chain; +mod config; +mod engine; +mod engine_error; +pub mod executor; +mod helpers; +#[cfg(feature = "memvid")] +pub mod long_term_memory; +pub mod router; +#[cfg(feature = "skills")] +pub mod skills; + +// ========================================================================= +// Re-exports from spider_agent_types (all pure data types) +// ========================================================================= + +pub use spider_agent_types::{ + // Config types + arena_rank, + // Schema generation types + build_schema_generation_prompt, + // Map result types + categories, + effective_thinking_budget, + effective_thinking_payload, + // Helpers + extract_assistant_content, + // Self-healing types + extract_html_context, + extract_last_code_block, + extract_last_json_array, + extract_last_json_boundaries, + extract_last_json_object, + extract_thinking_content, + extract_usage, + fnv1a64, + generate_schema, + infer_schema, + infer_schema_from_examples, + is_anthropic_endpoint, + is_url_allowed, + merged_config, + model_profile, + // Tool calling types + parse_tool_calls, + reasoning_payload, + refine_schema, + supports_pdf, + supports_video, + supports_vision, + thinking_payload, + tool_calls_to_steps, + truncate_utf8_tail, + // Observation types + ActResult, + // Actions + ActionRecord, + ActionResult, + ActionToolSchemas, + ActionType, + // Confidence types + Alternative, + AutomationConfig, + // Memory operations + AutomationMemory, + // Top-level types + AutomationResult, + AutomationUsage, + CaptureProfile, + // Chain types + ChainBuilder, + ChainCondition, + ChainContext, + ChainResult, + ChainStep, + ChainStepResult, + // HTML diff types + ChangeType, + // Planning types + Checkpoint, + CheckpointResult, + CheckpointType, + CleaningIntent, + ClipViewport, + // Concurrent chain types (data only) + ConcurrentChainConfig, + ConcurrentChainResult, + ConfidenceRetryStrategy, + ConfidenceSummary, + ConfidenceTracker, + ConfidentStep, + // Content analysis + ContentAnalysis, + CostTier, + DependencyGraph, + DependentStep, + DiffStats, + DiscoveredUrl, + ElementChange, + ExecutionPlan, + ExtractionSchema, + FormField, + FormInfo, + FunctionCall, + FunctionDefinition, + GeneratedSchema, + HealedSelectorCache, + HealingDiagnosis, + HealingRequest, + HealingResult, + HealingStats, + HtmlCleaningProfile, + HtmlDiffMode, + HtmlDiffResult, + InteractiveElement, + MapResult, + MemoryOperation, + ModelCapabilities, + ModelEndpoint, + ModelInfoEntry, + ModelPolicy, + ModelPricing, + ModelProfile, + ModelRanks, + // Synthesis types + MultiPageContext, + NavigationOption, + PageContext, + PageContribution, + PageObservation, + PageState, + PageStateDiff, + PlanExecutionState, + PlannedStep, + PlanningModeConfig, + PromptUrlGate, + ReasoningEffort, + RecoveryStrategy, + RemoteMultimodalConfig, + ReplanContext, + RetryPolicy, + SchemaCache, + SchemaGenerationRequest, + // Selector cache + SelectorCache, + SelectorCacheEntry, + SelectorIssueType, + SelfHealingConfig, + StepResult, + StructuredOutputConfig, + SynthesisConfig, + SynthesisResult, + ToolCall, + ToolCallingMode, + ToolDefinition, + Verification, + VerificationType, + VisionRouteMode, + // Prompts + ACT_SYSTEM_PROMPT, + CHROME_AI_SYSTEM_PROMPT, + CONFIGURATION_SYSTEM_PROMPT, + DEFAULT_SYSTEM_PROMPT, + EXTRACTION_ONLY_SYSTEM_PROMPT, + EXTRACT_SYSTEM_PROMPT, + MAP_SYSTEM_PROMPT, + MODEL_INFO, + OBSERVE_SYSTEM_PROMPT, +}; + +// ========================================================================= +// Re-exports from spider_agent_html (HTML cleaning utilities) +// ========================================================================= + +pub use spider_agent_html::{ + clean_html, clean_html_base, clean_html_full, clean_html_raw, clean_html_slim, + clean_html_with_profile, clean_html_with_profile_and_intent, smart_clean_html, +}; + +// ========================================================================= +// Re-exports from local modules (runtime components) +// ========================================================================= + +// Re-export RemoteMultimodalConfigs from local config (has heavy deps) +pub use config::RemoteMultimodalConfigs; + +// Re-export engine +pub use engine::RemoteMultimodalEngine; + +// Re-export error types +pub use engine_error::{EngineError, EngineResult}; + +// Re-export helpers that depend on EngineError +pub use helpers::best_effort_parse_json_object; + +// Re-export concurrent chain execution (needs tokio) +pub use concurrent_chain::execute_graph; + +// Re-export long-term memory types (memvid feature) +#[cfg(feature = "memvid")] +pub use long_term_memory::{ + ExperienceMemory, ExperienceMemoryConfig, ExperienceOutcome, ExperienceRecord, MemoryStats, + RecalledExperience, +}; + +// Re-export browser functions (chrome feature) +#[cfg(feature = "chrome")] +pub use browser::{ + run_remote_multimodal_with_page, run_spawn_pages_concurrent, run_spawn_pages_with_factory, + run_spawn_pages_with_options, PageFactory, PageSetupFn, SpawnPageOptions, SpawnedPageResult, +}; diff --git a/spider_agent/src/automation/router.rs b/spider_agent/src/automation/router.rs new file mode 100644 index 000000000..ee574fa8b --- /dev/null +++ b/spider_agent/src/automation/router.rs @@ -0,0 +1,1769 @@ +//! Smart model routing for optimal performance and cost. +//! +//! Routes requests to appropriate models based on: +//! - Task complexity and category +//! - Token count estimates +//! - Latency requirements +//! - Cost constraints +//! - Arena rankings and model capabilities (from `llm_models_spider`) +//! +//! ## Multi-Agent Model Selection +//! +//! The [`ModelSelector`] allows users to pass in their available models and +//! get optimal routing based on task type, arena rankings, and pricing data. +//! Users can also define custom rank/priority overrides. + +use super::{CostTier, ModelPolicy}; +use std::time::Duration; + +/// Smart router for selecting optimal models. +/// +/// Analyzes tasks and routes them to the most appropriate model +/// based on complexity, cost, and latency requirements. +#[derive(Debug, Clone)] +pub struct ModelRouter { + /// Model policy configuration. + policy: ModelPolicy, + /// Token threshold for using larger models. + large_model_threshold: usize, + /// Token threshold for using medium models. + medium_model_threshold: usize, + /// Whether to enable smart routing. + enabled: bool, +} + +impl Default for ModelRouter { + fn default() -> Self { + Self::new() + } +} + +impl ModelRouter { + /// Create a new router with default settings. + pub fn new() -> Self { + Self { + policy: ModelPolicy::default(), + large_model_threshold: 4000, + medium_model_threshold: 1000, + enabled: true, + } + } + + /// Create with custom policy. + pub fn with_policy(policy: ModelPolicy) -> Self { + Self { + policy, + ..Default::default() + } + } + + /// Set token thresholds. + pub fn with_thresholds(mut self, medium: usize, large: usize) -> Self { + self.medium_model_threshold = medium; + self.large_model_threshold = large; + self + } + + /// Get a reference to the underlying policy. + pub fn policy(&self) -> &ModelPolicy { + &self.policy + } + + /// Enable or disable smart routing. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Route a task to the optimal model. + /// + /// Returns the recommended model name. + pub fn route(&self, task: &TaskAnalysis) -> RoutingDecision { + if !self.enabled { + return RoutingDecision { + model: self.policy.medium.clone(), + tier: CostTier::Medium, + reason: "Smart routing disabled".to_string(), + }; + } + + // Determine complexity tier + let tier = self.analyze_complexity(task); + + // Check policy constraints + let tier = self.apply_constraints(tier, task); + + let model = self.policy.model_for_tier(tier).to_string(); + let reason = self.explain_routing(task, tier); + + RoutingDecision { + model, + tier, + reason, + } + } + + /// Analyze task complexity. + fn analyze_complexity(&self, task: &TaskAnalysis) -> CostTier { + let mut score = 0; + + // Token count factor + if task.estimated_tokens > self.large_model_threshold { + score += 3; + } else if task.estimated_tokens > self.medium_model_threshold { + score += 2; + } else { + score += 1; + } + + // Complexity indicators + if task.requires_reasoning { + score += 2; + } + if task.requires_code_generation { + score += 2; + } + if task.requires_structured_output { + score += 1; + } + if task.multi_step { + score += 1; + } + + // Map score to tier + match score { + 0..=2 => CostTier::Low, + 3..=5 => CostTier::Medium, + _ => CostTier::High, + } + } + + /// Apply policy constraints to the selected tier. + fn apply_constraints(&self, tier: CostTier, task: &TaskAnalysis) -> CostTier { + // Check max tier constraint + let tier = match (tier, self.policy.max_cost_tier) { + (CostTier::High, CostTier::Low) => CostTier::Low, + (CostTier::High, CostTier::Medium) => CostTier::Medium, + (CostTier::Medium, CostTier::Low) => CostTier::Low, + _ => tier, + }; + + // Check latency constraint + if let Some(max_latency) = self.policy.max_latency_ms { + let estimated_latency = self.estimate_latency(tier, task); + if estimated_latency > max_latency { + // Downgrade to faster model + return match tier { + CostTier::High => CostTier::Medium, + CostTier::Medium => CostTier::Low, + CostTier::Low => CostTier::Low, + }; + } + } + + // Check if large model is allowed + if tier == CostTier::High && !self.policy.allow_large { + return CostTier::Medium; + } + + tier + } + + /// Estimate latency for a tier. + fn estimate_latency(&self, tier: CostTier, task: &TaskAnalysis) -> u64 { + // Rough estimates in milliseconds + let base_latency = match tier { + CostTier::Low => 500, + CostTier::Medium => 1500, + CostTier::High => 3000, + }; + + // Add token-based estimate (rough: 50ms per 100 tokens) + let token_latency = (task.estimated_tokens as u64 / 100) * 50; + + base_latency + token_latency + } + + /// Explain the routing decision. + fn explain_routing(&self, task: &TaskAnalysis, tier: CostTier) -> String { + let mut reasons = Vec::new(); + + if task.estimated_tokens > self.large_model_threshold { + reasons.push("high token count"); + } + if task.requires_reasoning { + reasons.push("requires reasoning"); + } + if task.requires_code_generation { + reasons.push("requires code generation"); + } + + if reasons.is_empty() { + reasons.push("standard task"); + } + + format!("{:?} tier selected: {}", tier, reasons.join(", ")) + } + + /// Quickly route a simple prompt. + pub fn route_simple(&self, prompt: &str) -> RoutingDecision { + let task = TaskAnalysis::from_prompt(prompt); + self.route(&task) + } +} + +// ── ModelSelector ───────────────────────────────────────────────────────────── + +/// Capability requirements for model selection. +#[derive(Debug, Clone, Copy, Default)] +pub struct ModelRequirements { + /// Requires vision/image input support. + pub vision: bool, + /// Requires audio input support. + pub audio: bool, + /// Requires video input support. + pub video: bool, + /// Requires PDF/file input support. + pub pdf: bool, + /// Minimum context window (input tokens). 0 = no requirement. + pub min_context_tokens: u32, + /// Maximum input cost per million tokens in USD. 0.0 = no limit. + pub max_input_cost_per_m: f32, + /// Minimum arena score (0.0-100.0). 0.0 = no minimum. + pub min_arena_score: f32, +} + +impl ModelRequirements { + /// Require vision support. + pub fn with_vision(mut self) -> Self { + self.vision = true; + self + } + + /// Require a minimum context window. + pub fn with_min_context(mut self, tokens: u32) -> Self { + self.min_context_tokens = tokens; + self + } + + /// Set a maximum input cost per million tokens. + pub fn with_max_cost(mut self, cost: f32) -> Self { + self.max_input_cost_per_m = cost; + self + } + + /// Require a minimum arena score. + pub fn with_min_arena(mut self, score: f32) -> Self { + self.min_arena_score = score; + self + } +} + +/// A scored model candidate for selection. +#[derive(Debug, Clone)] +pub struct ScoredModel { + /// Model name (borrowed from the user's pool or MODEL_INFO). + pub name: String, + /// Effective score used for ranking (higher is better). + pub score: f32, + /// Arena rank if available (0.0-100.0). + pub arena_rank: Option, + /// Input cost per million tokens if available. + pub input_cost_per_m: Option, + /// Whether the model supports vision. + pub supports_vision: bool, + /// Max input tokens. + pub max_input_tokens: u32, +} + +/// Priority strategy for scoring models. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum SelectionStrategy { + /// Prefer highest arena score (quality). + #[default] + BestQuality, + /// Prefer lowest cost. + CheapestFirst, + /// Prefer largest context window. + LargestContext, + /// Balance quality and cost (arena_score / cost). + ValueOptimal, +} + +/// Flexible model selector that picks optimal models from a user-provided pool. +/// +/// Users pass in the models they have available (API keys, endpoints), and the +/// selector uses arena rankings, pricing, and capability data to rank them. +/// Custom priority overrides let users boost or penalize specific models. +/// +/// # Example +/// +/// ```rust,ignore +/// use spider_agent::automation::router::{ModelSelector, ModelRequirements, SelectionStrategy}; +/// +/// let mut selector = ModelSelector::new(&["gpt-4o", "claude-sonnet-4.5", "gemini-2.5-pro"]); +/// selector.set_strategy(SelectionStrategy::BestQuality); +/// +/// // Pick best model for a vision task +/// let reqs = ModelRequirements::default().with_vision(); +/// if let Some(best) = selector.select(&reqs) { +/// println!("Use: {} (score: {:.1})", best.name, best.score); +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct ModelSelector { + /// User's available models with optional custom priority overrides. + /// Each entry: (model_name, custom_priority_override). + /// Priority override: None = use auto scoring; Some(f32) = fixed score. + models: Vec<(String, Option)>, + /// Selection strategy. + strategy: SelectionStrategy, +} + +impl ModelSelector { + /// Create a selector from a list of available model names. + pub fn new(models: &[&str]) -> Self { + Self { + models: models.iter().map(|m| (m.to_lowercase(), None)).collect(), + strategy: SelectionStrategy::default(), + } + } + + /// Create from owned strings. + pub fn from_owned(models: Vec) -> Self { + Self { + models: models + .into_iter() + .map(|m| (m.to_lowercase(), None)) + .collect(), + strategy: SelectionStrategy::default(), + } + } + + /// Set the selection strategy. + pub fn set_strategy(&mut self, strategy: SelectionStrategy) { + self.strategy = strategy; + } + + /// Set a custom priority override for a specific model. + /// + /// The priority is a fixed score (higher = more preferred). + /// This overrides the auto-calculated score from arena/pricing data. + pub fn set_priority(&mut self, model: &str, priority: f32) { + let lower = model.to_lowercase(); + for (name, prio) in &mut self.models { + if *name == lower { + *prio = Some(priority); + return; + } + } + // Model not in pool — add it with the override + self.models.push((lower, Some(priority))); + } + + /// Add a model to the pool. + pub fn add_model(&mut self, model: &str) { + let lower = model.to_lowercase(); + if !self.models.iter().any(|(n, _)| *n == lower) { + self.models.push((lower, None)); + } + } + + /// Select the best model matching the given requirements. + /// + /// Returns `None` if no model in the pool satisfies the requirements. + /// + /// For pools with ≤ 2 models, skips scoring/sorting and returns the first + /// model that satisfies the requirements. Use the full ranking pipeline + /// only when there are 3+ models to meaningfully choose between. + pub fn select(&self, reqs: &ModelRequirements) -> Option { + if self.models.len() <= 2 { + // Fast path: no meaningful selection with 0-2 models. + // Just check requirements and return the first match. + return self + .models + .iter() + .filter_map(|(name, custom_prio)| self.score_model(name, *custom_prio, reqs)) + .next(); + } + self.ranked(reqs).into_iter().next() + } + + /// Return all models that satisfy the requirements, ranked best-to-worst. + /// + /// For pools with ≤ 2 models, skips the sorting step since the ordering + /// is trivial. Scoring/sorting is only worthwhile with 3+ candidates. + pub fn ranked(&self, reqs: &ModelRequirements) -> Vec { + let mut candidates: Vec = self + .models + .iter() + .filter_map(|(name, custom_prio)| self.score_model(name, *custom_prio, reqs)) + .collect(); + + // Only worth sorting when there are 3+ candidates + if candidates.len() > 2 { + candidates.sort_by(|a, b| { + b.score + .partial_cmp(&a.score) + .unwrap_or(std::cmp::Ordering::Equal) + }); + } + candidates + } + + /// Select the best model for each distinct requirement set from a list. + /// + /// Useful for multi-agent dispatch: given N different sub-tasks with different + /// requirements, get the best model for each without reusing the same model + /// (unless it's the only option). + pub fn select_multi(&self, requirements: &[ModelRequirements]) -> Vec> { + let mut used: Vec = vec![false; self.models.len()]; + let mut results = Vec::with_capacity(requirements.len()); + + for reqs in requirements { + let mut best: Option<(ScoredModel, usize)> = None; + + for (idx, (name, custom_prio)) in self.models.iter().enumerate() { + if used[idx] { + continue; + } + if let Some(scored) = self.score_model(name, *custom_prio, reqs) { + let dominated = match &best { + Some((current, _)) => scored.score > current.score, + None => true, + }; + if dominated { + best = Some((scored, idx)); + } + } + } + + if let Some((model, idx)) = best { + used[idx] = true; + results.push(Some(model)); + } else { + // Fallback: allow reuse if no unused model fits + let fallback = self.select(reqs); + results.push(fallback); + } + } + + results + } + + /// Score a model against requirements. Returns None if it doesn't meet them. + fn score_model( + &self, + name: &str, + custom_prio: Option, + reqs: &ModelRequirements, + ) -> Option { + let profile = llm_models_spider::model_profile(name); + + // Extract capabilities (from profile or from individual lookups) + let has_vision = profile + .as_ref() + .map(|p| p.capabilities.vision) + .unwrap_or_else(|| llm_models_spider::supports_vision(name)); + let has_audio = profile + .as_ref() + .map(|p| p.capabilities.audio) + .unwrap_or(false); + let has_video = profile + .as_ref() + .map(|p| p.capabilities.video) + .unwrap_or_else(|| llm_models_spider::supports_video(name)); + let has_pdf = profile + .as_ref() + .map(|p| p.capabilities.file) + .unwrap_or_else(|| llm_models_spider::supports_pdf(name)); + + // Check hard requirements + if reqs.vision && !has_vision { + return None; + } + if reqs.audio && !has_audio { + return None; + } + if reqs.video && !has_video { + return None; + } + if reqs.pdf && !has_pdf { + return None; + } + + let max_input = profile.as_ref().map(|p| p.max_input_tokens).unwrap_or(0); + if reqs.min_context_tokens > 0 && max_input < reqs.min_context_tokens { + return None; + } + + let arena = profile.as_ref().and_then(|p| p.ranks.overall); + let input_cost = profile + .as_ref() + .and_then(|p| p.pricing.input_cost_per_m_tokens); + + if reqs.max_input_cost_per_m > 0.0 { + if let Some(cost) = input_cost { + if cost > reqs.max_input_cost_per_m { + return None; + } + } + } + + if reqs.min_arena_score > 0.0 { + match arena { + Some(score) if score >= reqs.min_arena_score => {} + Some(_) => return None, + None => {} // Unknown arena — don't filter out + } + } + + // Compute score + let score = if let Some(prio) = custom_prio { + prio + } else { + self.auto_score(arena, input_cost, max_input) + }; + + Some(ScoredModel { + name: name.to_string(), + score, + arena_rank: arena, + input_cost_per_m: input_cost, + supports_vision: has_vision, + max_input_tokens: max_input, + }) + } + + /// Compute an automatic score based on strategy. + fn auto_score(&self, arena: Option, cost: Option, context: u32) -> f32 { + match self.strategy { + SelectionStrategy::BestQuality => arena.unwrap_or(50.0), + SelectionStrategy::CheapestFirst => { + // Invert cost: lower cost = higher score + match cost { + Some(c) if c > 0.0 => 1000.0 / c, + _ => 50.0, // Unknown cost = neutral + } + } + SelectionStrategy::LargestContext => context as f32 / 1000.0, + SelectionStrategy::ValueOptimal => { + let quality = arena.unwrap_or(50.0); + let cost_factor = match cost { + Some(c) if c > 0.0 => 100.0 / c, + _ => 1.0, + }; + quality * cost_factor.sqrt() + } + } + } +} + +/// Build a [`ModelPolicy`] automatically from a pool of available models. +/// +/// Inspects arena rankings and pricing to assign models to tiers. +/// The best-ranked model becomes `large`, cheapest becomes `small`, +/// and something in-between becomes `medium`. +/// +/// For pools with ≤ 2 models, skips the full scoring pipeline: +/// - 0 models → default policy +/// - 1 model → all tiers use that model +/// - 2 models → first=large/medium, second=small (no scoring needed, +/// dual-model routing via [`VisionRouteMode`] handles the rest) +pub fn auto_policy(available_models: &[&str]) -> ModelPolicy { + if available_models.is_empty() { + return ModelPolicy::default(); + } + if available_models.len() == 1 { + let m = available_models[0].to_string(); + return ModelPolicy { + small: m.clone(), + medium: m.clone(), + large: m, + allow_large: true, + max_latency_ms: None, + max_cost_tier: CostTier::High, + }; + } + if available_models.len() == 2 { + // With only 2 models, skip arena/pricing lookups. + // Assign first as large/medium, second as small — the caller + // already knows which is vision vs text via VisionRouteMode. + let a = available_models[0].to_string(); + let b = available_models[1].to_string(); + return ModelPolicy { + large: a.clone(), + medium: a, + small: b, + allow_large: true, + max_latency_ms: None, + max_cost_tier: CostTier::High, + }; + } + + // 3+ models: full scoring pipeline + // Collect (name, arena_score, input_cost) + let mut models: Vec<(&str, f32, f32)> = available_models + .iter() + .map(|&name| { + let profile = llm_models_spider::model_profile(name); + let arena = profile + .as_ref() + .and_then(|p| p.ranks.overall) + .unwrap_or(50.0); + let cost = profile + .as_ref() + .and_then(|p| p.pricing.input_cost_per_m_tokens) + .unwrap_or(5.0); + (name, arena, cost) + }) + .collect(); + + // Sort by arena score descending + models.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); + + // Safety: models is guaranteed non-empty here (early returns above handle 0/1/2), + // but use defensive indexing to avoid panics if logic changes. + if models.is_empty() { + return ModelPolicy::default(); + } + let large = models[0].0.to_string(); + let small = models + .last() + .map(|m| m.0.to_string()) + .unwrap_or_else(|| large.clone()); + let medium = if models.len() >= 3 { + models[models.len() / 2].0.to_string() + } else { + large.clone() + }; + + ModelPolicy { + small, + medium, + large, + allow_large: true, + max_latency_ms: None, + max_cost_tier: CostTier::High, + } +} + +// ── TaskAnalysis ────────────────────────────────────────────────────────────── + +/// Analysis of a task for routing. +#[derive(Debug, Clone, Default)] +pub struct TaskAnalysis { + /// Estimated input tokens. + pub estimated_tokens: usize, + /// Whether the task requires complex reasoning. + pub requires_reasoning: bool, + /// Whether the task requires code generation. + pub requires_code_generation: bool, + /// Whether structured JSON output is required. + pub requires_structured_output: bool, + /// Whether this is a multi-step task. + pub multi_step: bool, + /// Maximum acceptable latency. + pub max_latency: Option, + /// Task category. + pub category: TaskCategory, + /// Whether the task requires vision capabilities. + pub requires_vision: bool, + /// Whether the task requires audio capabilities. + pub requires_audio: bool, +} + +impl TaskAnalysis { + /// Create analysis from a prompt. + pub fn from_prompt(prompt: &str) -> Self { + let estimated_tokens = estimate_tokens(prompt); + let lower = prompt.to_lowercase(); + + Self { + estimated_tokens, + requires_reasoning: lower.contains("analyze") + || lower.contains("compare") + || lower.contains("explain") + || lower.contains("why"), + requires_code_generation: lower.contains("code") + || lower.contains("implement") + || lower.contains("function") + || lower.contains("script"), + requires_structured_output: lower.contains("json") + || lower.contains("extract") + || lower.contains("list"), + multi_step: lower.contains("then") + || lower.contains("step") + || lower.contains("first") + || lower.contains("next"), + max_latency: None, + category: TaskCategory::General, + requires_vision: lower.contains("screenshot") + || lower.contains("image") + || lower.contains("picture") + || lower.contains("visual"), + requires_audio: lower.contains("audio") + || lower.contains("voice") + || lower.contains("speech"), + } + } + + /// Create analysis for extraction task. + pub fn extraction(html_length: usize) -> Self { + Self { + estimated_tokens: html_length / 4 + 200, // Rough estimate + requires_reasoning: false, + requires_code_generation: false, + requires_structured_output: true, + multi_step: false, + max_latency: None, + category: TaskCategory::Extraction, + requires_vision: false, + requires_audio: false, + } + } + + /// Create analysis for action task. + pub fn action(instruction: &str) -> Self { + let mut analysis = Self::from_prompt(instruction); + analysis.category = TaskCategory::Action; + analysis.requires_structured_output = true; + analysis + } + + /// Set max latency requirement. + pub fn with_max_latency(mut self, latency: Duration) -> Self { + self.max_latency = Some(latency); + self + } + + /// Convert to model requirements for the selector. + pub fn to_requirements(&self) -> ModelRequirements { + ModelRequirements { + vision: self.requires_vision, + audio: self.requires_audio, + ..Default::default() + } + } +} + +/// Category of task. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum TaskCategory { + /// General purpose task. + #[default] + General, + /// Data extraction. + Extraction, + /// Browser action. + Action, + /// Code generation. + Code, + /// Analysis/reasoning. + Analysis, + /// Simple classification. + Classification, +} + +/// Result of routing decision. +#[derive(Debug, Clone)] +pub struct RoutingDecision { + /// Selected model name. + pub model: String, + /// Selected cost tier. + pub tier: CostTier, + /// Explanation for the decision. + pub reason: String, +} + +impl RoutingDecision { + /// Check if this routes to a fast model. + pub fn is_fast(&self) -> bool { + self.tier == CostTier::Low + } + + /// Check if this routes to a powerful model. + pub fn is_powerful(&self) -> bool { + self.tier == CostTier::High + } +} + +/// Estimate token count for text. +/// +/// Uses a rough approximation of 4 characters per token. +pub fn estimate_tokens(text: &str) -> usize { + // Rough estimate: ~4 characters per token for English + // This is a simplification; real tokenization is more complex + text.len() / 4 + 1 +} + +/// Estimate tokens for messages. +pub fn estimate_message_tokens(messages: &[crate::Message]) -> usize { + messages + .iter() + .map(|m| estimate_tokens(m.content.as_text()) + 4) // +4 for message overhead + .sum() +} + +/// Classify the complexity of an automation round using signals already +/// available in the engine loop — no additional LLM call required. +/// +/// Returns a [`TaskAnalysis`] suitable for passing to [`ModelRouter::route`]. +pub fn classify_round_complexity( + user_prompt: &str, + html_len: usize, + round_idx: usize, + stagnated: bool, +) -> TaskAnalysis { + let mut analysis = TaskAnalysis::from_prompt(user_prompt); + analysis.estimated_tokens = user_prompt.len() / 4 + 1; + // Initial round requires more reasoning (first page analysis) + if round_idx == 0 { + analysis.requires_reasoning = true; + } + // Stagnated rounds need a stronger model to break out + if stagnated { + analysis.requires_reasoning = true; + analysis.multi_step = true; + } + // Large pages need more capability + if html_len > 50_000 { + analysis.requires_reasoning = true; + } + // Automation always requires structured JSON output + analysis.requires_structured_output = true; + analysis +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_model_router_simple() { + let router = ModelRouter::new(); + + let decision = router.route_simple("Extract the title from this page"); + assert!(!decision.model.is_empty()); + } + + #[test] + fn test_model_router_complex() { + let router = ModelRouter::new(); + + let task = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + + let decision = router.route(&task); + assert_eq!(decision.tier, CostTier::High); + } + + #[test] + fn test_model_router_constrained() { + let policy = ModelPolicy { + max_cost_tier: CostTier::Medium, + ..Default::default() + }; + + let router = ModelRouter::with_policy(policy); + + let task = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + ..Default::default() + }; + + let decision = router.route(&task); + // Should be capped at Medium due to policy + assert!(decision.tier != CostTier::High); + } + + #[test] + fn test_task_analysis_from_prompt() { + let analysis = TaskAnalysis::from_prompt( + "Analyze the code and explain why it's slow, then implement a fix", + ); + + assert!(analysis.requires_reasoning); + assert!(analysis.requires_code_generation); + assert!(analysis.multi_step); + } + + #[test] + fn test_task_analysis_vision_detection() { + let analysis = TaskAnalysis::from_prompt("Look at this screenshot and describe it"); + assert!(analysis.requires_vision); + + let analysis = TaskAnalysis::from_prompt("Summarize this text"); + assert!(!analysis.requires_vision); + } + + #[test] + fn test_estimate_tokens() { + assert_eq!(estimate_tokens("hello world"), 3); // 11 chars / 4 + 1 + assert_eq!(estimate_tokens(""), 1); + } + + // ── ModelSelector tests ─────────────────────────────────────────────── + + #[test] + fn test_model_selector_basic() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + let reqs = ModelRequirements::default(); + let result = selector.select(&reqs); + assert!(result.is_some()); + } + + #[test] + fn test_model_selector_vision_filter() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-3.5-turbo"]); + let reqs = ModelRequirements::default().with_vision(); + let ranked = selector.ranked(&reqs); + + // gpt-4o supports vision, gpt-3.5-turbo does not + assert!(!ranked.is_empty()); + for m in &ranked { + assert!( + m.supports_vision, + "non-vision model {} passed filter", + m.name + ); + } + } + + #[test] + fn test_model_selector_custom_priority() { + let mut selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + // Override gpt-4o-mini to be top priority + selector.set_priority("gpt-4o-mini", 999.0); + + let reqs = ModelRequirements::default(); + let best = selector.select(&reqs).unwrap(); + assert_eq!(best.name, "gpt-4o-mini"); + assert_eq!(best.score, 999.0); + } + + #[test] + fn test_model_selector_cheapest_strategy() { + let mut selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini"]); + selector.set_strategy(SelectionStrategy::CheapestFirst); + + let reqs = ModelRequirements::default(); + let ranked = selector.ranked(&reqs); + + // With CheapestFirst, cheaper model should rank higher + assert!(ranked.len() >= 1); + } + + #[test] + fn test_model_selector_multi_dispatch() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + + let requirements = vec![ + ModelRequirements::default().with_vision(), + ModelRequirements::default(), + ]; + + let results = selector.select_multi(&requirements); + assert_eq!(results.len(), 2); + + // First task needs vision — should pick a vision model + assert!(results[0].is_some()); + assert!(results[0].as_ref().unwrap().supports_vision); + + // Second task — should pick a different model if possible + assert!(results[1].is_some()); + } + + #[test] + fn test_model_selector_add_model() { + let mut selector = ModelSelector::new(&["gpt-4o"]); + selector.add_model("gpt-4o-mini"); + assert_eq!(selector.models.len(), 2); + // Adding duplicate should not increase count + selector.add_model("gpt-4o"); + assert_eq!(selector.models.len(), 2); + } + + #[test] + fn test_auto_policy_single_model() { + let policy = auto_policy(&["gpt-4o"]); + assert_eq!(policy.small, "gpt-4o"); + assert_eq!(policy.medium, "gpt-4o"); + assert_eq!(policy.large, "gpt-4o"); + } + + #[test] + fn test_auto_policy_multiple_models() { + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + // Should assign different models to different tiers + assert!(!policy.small.is_empty()); + assert!(!policy.medium.is_empty()); + assert!(!policy.large.is_empty()); + } + + #[test] + fn test_auto_policy_empty() { + let policy = auto_policy(&[]); + // Should return default policy + assert_eq!(policy.small, "gpt-4o-mini"); + assert_eq!(policy.medium, "gpt-4o"); + } + + #[test] + fn test_model_requirements_builder() { + let reqs = ModelRequirements::default() + .with_vision() + .with_min_context(100_000) + .with_max_cost(10.0) + .with_min_arena(60.0); + + assert!(reqs.vision); + assert_eq!(reqs.min_context_tokens, 100_000); + assert_eq!(reqs.max_input_cost_per_m, 10.0); + assert_eq!(reqs.min_arena_score, 60.0); + } + + #[test] + fn test_task_to_requirements() { + let task = TaskAnalysis::from_prompt("Look at this screenshot and extract data"); + let reqs = task.to_requirements(); + assert!(reqs.vision); + } + + // ── Phase 1: llm_models_spider data accuracy ──────────────────────── + + #[test] + fn test_llm_data_vision_models_detected() { + // Models that MUST report vision support + for model in &[ + "gpt-4o", + "gpt-4o-mini", + "claude-sonnet-4-5-20250514", + "gemini-2.0-flash", + "qwen2-vl-72b-instruct", + "llama-3.2-11b-vision-instruct", + ] { + assert!( + llm_models_spider::supports_vision(model), + "{model} should support vision" + ); + } + // Models that MUST NOT report vision support + for model in &["gpt-3.5-turbo", "deepseek-chat"] { + assert!( + !llm_models_spider::supports_vision(model), + "{model} should NOT support vision" + ); + } + } + + #[test] + fn test_llm_data_model_profiles_exist() { + let must_have = [ + "gpt-4o", + "gpt-4o-mini", + "gpt-3.5-turbo", + "claude-3-5-sonnet-20241022", + "gemini-2.0-flash", + "deepseek-chat", + ]; + for name in &must_have { + let profile = llm_models_spider::model_profile(name); + assert!( + profile.is_some(), + "model_profile({name}) should return Some" + ); + let p = profile.unwrap(); + assert!( + p.max_input_tokens > 0, + "{name} should have max_input_tokens > 0, got {}", + p.max_input_tokens + ); + } + } + + #[test] + fn test_llm_data_arena_scores_present() { + // These well-known models should have arena scores + // (use short canonical names that match the arena data) + for name in &["claude-3.5-sonnet", "chatgpt-4o-latest", "claude-opus-4"] { + let profile = llm_models_spider::model_profile(name); + assert!(profile.is_some(), "{name} should have a profile"); + let p = profile.unwrap(); + assert!( + p.ranks.overall.is_some(), + "{name} should have an arena score" + ); + assert!( + p.ranks.overall.unwrap() > 0.0, + "{name} arena score should be > 0" + ); + } + } + + #[test] + fn test_llm_data_pricing_ordering() { + let cheap = llm_models_spider::model_profile("gpt-4o-mini"); + let expensive = llm_models_spider::model_profile("claude-opus-4-20250514"); + assert!(cheap.is_some() && expensive.is_some()); + let cheap_cost = cheap.unwrap().pricing.input_cost_per_m_tokens.unwrap(); + let expensive_cost = expensive.unwrap().pricing.input_cost_per_m_tokens.unwrap(); + assert!( + cheap_cost < expensive_cost, + "gpt-4o-mini (${cheap_cost}) should be cheaper than claude-opus-4 (${expensive_cost})" + ); + } + + #[test] + fn test_llm_data_context_window_ordering() { + let large_ctx = llm_models_spider::model_profile("gemini-2.5-pro-preview-05-06"); + let small_ctx = llm_models_spider::model_profile("gpt-3.5-turbo"); + assert!(large_ctx.is_some() && small_ctx.is_some()); + let large_tokens = large_ctx.unwrap().max_input_tokens; + let small_tokens = small_ctx.unwrap().max_input_tokens; + assert!( + large_tokens > small_tokens, + "gemini-2.5-pro ({large_tokens}) should have more context than gpt-3.5-turbo ({small_tokens})" + ); + } + + // ── Phase 2: ModelSelector reliability ────────────────────────────── + + #[test] + fn test_selector_realistic_pool_best_quality() { + let selector = ModelSelector::new(&[ + "gpt-4o", + "gpt-4o-mini", + "gpt-3.5-turbo", + "claude-3-5-sonnet-20241022", + "gemini-2.0-flash", + "deepseek-chat", + ]); + let reqs = ModelRequirements::default(); + let ranked = selector.ranked(&reqs); + assert!(!ranked.is_empty()); + // BestQuality (default) → first should have the highest arena score + let top = &ranked[0]; + for other in &ranked[1..] { + assert!( + top.score >= other.score, + "top model {} (score {}) should beat {} (score {})", + top.name, + top.score, + other.name, + other.score + ); + } + } + + #[test] + fn test_selector_realistic_pool_cheapest() { + let mut selector = ModelSelector::new(&[ + "gpt-4o", + "gpt-4o-mini", + "gpt-3.5-turbo", + "claude-3-5-sonnet-20241022", + ]); + selector.set_strategy(SelectionStrategy::CheapestFirst); + let reqs = ModelRequirements::default(); + let ranked = selector.ranked(&reqs); + assert!(ranked.len() >= 2); + // CheapestFirst → lower cost gets higher score + let top = &ranked[0]; + let bottom = ranked.last().unwrap(); + if let (Some(top_cost), Some(bottom_cost)) = (top.input_cost_per_m, bottom.input_cost_per_m) + { + assert!( + top_cost <= bottom_cost, + "cheapest ({}, ${top_cost}) should rank above expensive ({}, ${bottom_cost})", + top.name, + bottom.name + ); + } + } + + #[test] + fn test_selector_vision_filter_rejects_text_only() { + let selector = ModelSelector::new(&["gpt-3.5-turbo", "deepseek-chat"]); + let reqs = ModelRequirements::default().with_vision(); + let result = selector.select(&reqs); + assert!( + result.is_none(), + "text-only pool should return None for vision requirement" + ); + } + + #[test] + fn test_selector_unknown_models_graceful() { + let selector = ModelSelector::new(&["my-custom-model", "local-llama"]); + let reqs = ModelRequirements::default(); + let result = selector.select(&reqs); + assert!( + result.is_some(), + "unknown models should still return Some with default score" + ); + let scored = result.unwrap(); + assert_eq!(scored.score, 50.0, "unknown model gets default score 50.0"); + } + + #[test] + fn test_selector_single_model_all_strategies() { + for strategy in &[ + SelectionStrategy::BestQuality, + SelectionStrategy::CheapestFirst, + SelectionStrategy::LargestContext, + SelectionStrategy::ValueOptimal, + ] { + let mut selector = ModelSelector::new(&["gpt-4o"]); + selector.set_strategy(*strategy); + let reqs = ModelRequirements::default(); + let result = selector.select(&reqs); + assert!( + result.is_some(), + "single model should be returned for {strategy:?}" + ); + assert_eq!(result.unwrap().name, "gpt-4o"); + } + } + + #[test] + fn test_selector_deterministic_ordering() { + let selector = ModelSelector::new(&[ + "gpt-4o", + "gpt-4o-mini", + "claude-3-5-sonnet-20241022", + "gemini-2.0-flash", + ]); + let reqs = ModelRequirements::default(); + + let first_run: Vec = selector + .ranked(&reqs) + .iter() + .map(|m| m.name.clone()) + .collect(); + let second_run: Vec = selector + .ranked(&reqs) + .iter() + .map(|m| m.name.clone()) + .collect(); + assert_eq!( + first_run, second_run, + "repeated calls must produce identical ordering" + ); + } + + #[test] + fn test_selector_cost_filter_strict() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + let reqs = ModelRequirements::default().with_max_cost(1.0); + let ranked = selector.ranked(&reqs); + for m in &ranked { + if let Some(cost) = m.input_cost_per_m { + assert!( + cost <= 1.0, + "{} has cost ${cost} which exceeds max 1.0", + m.name + ); + } + } + } + + #[test] + fn test_selector_min_context_filter() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-3.5-turbo", "gemini-2.0-flash"]); + let reqs = ModelRequirements::default().with_min_context(500_000); + let ranked = selector.ranked(&reqs); + for m in &ranked { + assert!( + m.max_input_tokens >= 500_000, + "{} has {} tokens, below 500k minimum", + m.name, + m.max_input_tokens + ); + } + } + + #[test] + fn test_selector_value_optimal_balances() { + let mut selector = ModelSelector::new(&[ + "gpt-4o", // high quality, moderate cost + "gpt-4o-mini", // lower quality, cheap + "gpt-3.5-turbo", // lowest quality, cheapest + ]); + selector.set_strategy(SelectionStrategy::ValueOptimal); + let reqs = ModelRequirements::default(); + let ranked = selector.ranked(&reqs); + assert!(!ranked.is_empty()); + let top = &ranked[0]; + // ValueOptimal should NOT just pick cheapest or best — verify it's not the raw cheapest + // (it uses quality * sqrt(100/cost), so moderate-cost + high-quality can win) + // Just verify it returns a valid result with a positive score + assert!(top.score > 0.0, "ValueOptimal score should be positive"); + } + + #[test] + fn test_select_multi_no_reuse() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + let requirements = vec![ + ModelRequirements::default(), + ModelRequirements::default(), + ModelRequirements::default(), + ]; + let results = selector.select_multi(&requirements); + assert_eq!(results.len(), 3); + // All should be Some + let names: Vec<&str> = results + .iter() + .filter_map(|r| r.as_ref().map(|m| m.name.as_str())) + .collect(); + assert_eq!(names.len(), 3, "all 3 requests should get a model"); + // No duplicates (3 models, 3 requests → each used once) + let mut deduped = names.clone(); + deduped.sort(); + deduped.dedup(); + assert_eq!( + deduped.len(), + 3, + "no model should be reused when pool is large enough" + ); + } + + #[test] + fn test_select_multi_exhaustion_fallback() { + let selector = ModelSelector::new(&["gpt-4o"]); + let requirements = vec![ + ModelRequirements::default(), + ModelRequirements::default(), + ModelRequirements::default(), + ]; + let results = selector.select_multi(&requirements); + assert_eq!(results.len(), 3); + // First gets the model, rest fall back to reuse + assert!(results[0].is_some()); + assert!( + results[1].is_some(), + "fallback should reuse the single model" + ); + assert!( + results[2].is_some(), + "fallback should reuse the single model" + ); + // All should be the same model + assert_eq!(results[0].as_ref().unwrap().name, "gpt-4o"); + assert_eq!(results[1].as_ref().unwrap().name, "gpt-4o"); + assert_eq!(results[2].as_ref().unwrap().name, "gpt-4o"); + } + + #[test] + fn test_selector_priority_override_beats_arena() { + let mut selector = ModelSelector::new(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + // gpt-3.5-turbo has low arena score; override it to beat gpt-4o + selector.set_priority("gpt-3.5-turbo", 999.0); + let reqs = ModelRequirements::default(); + let best = selector.select(&reqs).unwrap(); + assert_eq!( + best.name, "gpt-3.5-turbo", + "priority override should beat natural arena score" + ); + assert_eq!(best.score, 999.0); + } + + // ── Phase 3: auto_policy + ModelRouter pipeline ───────────────────── + + #[test] + fn test_auto_policy_realistic_tiering() { + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + // Sorted by arena descending: large=highest, small=lowest + // large should be the model with highest arena score + // small should be the model with lowest arena score + assert_ne!( + policy.large, policy.small, + "large and small should be different models" + ); + // Verify tiers resolve correctly + assert_eq!(policy.model_for_tier(CostTier::High), policy.large); + assert_eq!(policy.model_for_tier(CostTier::Low), policy.small); + assert_eq!(policy.model_for_tier(CostTier::Medium), policy.medium); + } + + #[test] + fn test_auto_policy_2_models() { + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini"]); + // With 2 models, skip scoring: first=large/medium, second=small + assert_eq!(policy.large, "gpt-4o"); + assert_eq!(policy.medium, "gpt-4o"); + assert_eq!(policy.small, "gpt-4o-mini"); + assert_eq!( + policy.medium, policy.large, + "2-model policy should have medium == large" + ); + assert_ne!(policy.large, policy.small, "large and small should differ"); + } + + #[test] + fn test_auto_policy_unknown_models() { + let policy = auto_policy(&["my-custom-llm", "local-model-7b", "test-endpoint"]); + // Should not panic, all get default arena=50.0, cost=5.0 + assert!(!policy.small.is_empty()); + assert!(!policy.medium.is_empty()); + assert!(!policy.large.is_empty()); + assert!(policy.allow_large); + } + + #[test] + fn test_auto_policy_to_router_e2e() { + let policy = auto_policy(&["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]); + let router = ModelRouter::with_policy(policy.clone()); + + // Low complexity → small model + let simple_task = TaskAnalysis { + estimated_tokens: 100, + ..Default::default() + }; + let decision = router.route(&simple_task); + assert_eq!(decision.tier, CostTier::Low); + assert_eq!(decision.model, policy.small); + + // High complexity → large model + let hard_task = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + let decision = router.route(&hard_task); + assert_eq!(decision.tier, CostTier::High); + assert_eq!(decision.model, policy.large); + + // Medium complexity → medium model + let medium_task = TaskAnalysis { + estimated_tokens: 2000, + requires_structured_output: true, + multi_step: true, + ..Default::default() + }; + let decision = router.route(&medium_task); + assert_eq!(decision.tier, CostTier::Medium); + assert_eq!(decision.model, policy.medium); + } + + #[test] + fn test_auto_policy_to_router_e2e_single_model() { + // Common case: user only has one API key / one model + let policy = auto_policy(&["gpt-4o"]); + assert_eq!(policy.small, "gpt-4o"); + assert_eq!(policy.medium, "gpt-4o"); + assert_eq!(policy.large, "gpt-4o"); + + let router = ModelRouter::with_policy(policy); + + // ALL complexity levels must resolve to the single model + let simple = TaskAnalysis { + estimated_tokens: 50, + ..Default::default() + }; + let medium = TaskAnalysis { + estimated_tokens: 2000, + requires_structured_output: true, + ..Default::default() + }; + let hard = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + + for (label, task) in [("simple", &simple), ("medium", &medium), ("hard", &hard)] { + let decision = router.route(task); + assert_eq!( + decision.model, "gpt-4o", + "{label} task should still route to the only model" + ); + } + } + + #[test] + fn test_selector_single_model_vision_mismatch() { + // User has one text-only model but needs vision → None + let selector = ModelSelector::new(&["gpt-3.5-turbo"]); + let reqs = ModelRequirements::default().with_vision(); + assert!( + selector.select(&reqs).is_none(), + "single text-only model should not satisfy vision requirement" + ); + + // User has one vision model and needs vision → works + let selector = ModelSelector::new(&["gpt-4o"]); + let result = selector.select(&reqs); + assert!( + result.is_some(), + "single vision model should satisfy vision" + ); + assert_eq!(result.unwrap().name, "gpt-4o"); + } + + #[test] + fn test_selector_single_model_with_cost_filter() { + // User has one expensive model but cost filter is strict → None + let selector = ModelSelector::new(&["gpt-4o"]); + let reqs = ModelRequirements::default().with_max_cost(0.01); + assert!( + selector.select(&reqs).is_none(), + "single expensive model should be filtered by strict cost limit" + ); + + // Relax cost filter → works + let reqs = ModelRequirements::default().with_max_cost(100.0); + let result = selector.select(&reqs); + assert!(result.is_some()); + assert_eq!(result.unwrap().name, "gpt-4o"); + } + + #[test] + fn test_selector_single_unknown_model_e2e() { + // User has one custom/self-hosted model with no data in llm_models_spider + let policy = auto_policy(&["my-local-llama"]); + assert_eq!(policy.small, "my-local-llama"); + assert_eq!(policy.medium, "my-local-llama"); + assert_eq!(policy.large, "my-local-llama"); + + let router = ModelRouter::with_policy(policy); + let decision = router.route_simple("do something complex and analyze the code"); + assert_eq!( + decision.model, "my-local-llama", + "unknown single model should still be routed to" + ); + + // Also verify selector works + let selector = ModelSelector::new(&["my-local-llama"]); + let result = selector.select(&ModelRequirements::default()); + assert!(result.is_some()); + let scored = result.unwrap(); + assert_eq!(scored.name, "my-local-llama"); + assert_eq!(scored.score, 50.0, "unknown model gets default score"); + assert_eq!( + scored.max_input_tokens, 0, + "unknown model has no context data" + ); + assert!( + scored.arena_rank.is_none(), + "unknown model has no arena data" + ); + } + + #[test] + fn test_router_latency_constraint_downgrade() { + let policy = ModelPolicy { + max_latency_ms: Some(1000), + ..Default::default() + }; + let router = ModelRouter::with_policy(policy); + + // A task that would normally be High tier + let task = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + let decision = router.route(&task); + // Latency constraint should downgrade from High + assert_ne!( + decision.tier, + CostTier::High, + "latency constraint should prevent High tier" + ); + } + + #[test] + fn test_router_allow_large_false() { + let policy = ModelPolicy { + allow_large: false, + ..Default::default() + }; + let router = ModelRouter::with_policy(policy); + + let task = TaskAnalysis { + estimated_tokens: 5000, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + let decision = router.route(&task); + assert_ne!( + decision.tier, + CostTier::High, + "allow_large=false should cap at Medium" + ); + } + + #[test] + fn test_router_threshold_customization() { + let router = ModelRouter::new().with_thresholds(100, 200); + + // With lowered thresholds: tokens=300 → +3 (> large threshold 200), + // reasoning → +2, code_gen → +2 = 7 → High tier + let task = TaskAnalysis { + estimated_tokens: 300, + requires_reasoning: true, + requires_code_generation: true, + ..Default::default() + }; + let decision = router.route(&task); + assert_eq!( + decision.tier, + CostTier::High, + "lowered thresholds should promote to High tier sooner" + ); + + // Same task with default thresholds (1000/4000) would be Medium: + // tokens=300 → +1 (< 1000), reasoning → +2, code_gen → +2 = 5 → Medium + let default_router = ModelRouter::new(); + let decision = default_router.route(&task); + assert_eq!( + decision.tier, + CostTier::Medium, + "default thresholds should keep this at Medium" + ); + } + + // ── Phase 5: Edge cases ───────────────────────────────────────────── + + #[test] + fn test_selector_empty_pool() { + let selector = ModelSelector::new(&[]); + let reqs = ModelRequirements::default(); + let result = selector.select(&reqs); + assert!(result.is_none(), "empty pool should return None"); + } + + #[test] + fn test_selector_duplicate_models() { + let selector = ModelSelector::new(&["gpt-4o", "gpt-4o", "gpt-4o"]); + let requirements = vec![ + ModelRequirements::default(), + ModelRequirements::default(), + ModelRequirements::default(), + ]; + let results = selector.select_multi(&requirements); + assert_eq!(results.len(), 3, "should not hang on duplicates"); + // All should resolve (first gets it, rest fallback) + for (i, r) in results.iter().enumerate() { + assert!(r.is_some(), "request {i} should get a model"); + } + } + + #[test] + fn test_task_analysis_edge_cases() { + // Empty string + let analysis = TaskAnalysis::from_prompt(""); + assert_eq!(analysis.estimated_tokens, 1); + assert!(!analysis.requires_reasoning); + + // All keywords + let analysis = TaskAnalysis::from_prompt( + "analyze compare explain why code implement function script json extract list then step first next screenshot image", + ); + assert!(analysis.requires_reasoning); + assert!(analysis.requires_code_generation); + assert!(analysis.requires_structured_output); + assert!(analysis.multi_step); + assert!(analysis.requires_vision); + + // Unicode-only + let analysis = TaskAnalysis::from_prompt("你好世界 🌍 日本語テスト"); + assert!(!analysis.requires_reasoning); + assert!(!analysis.requires_code_generation); + assert!(analysis.estimated_tokens > 0); + } + + #[test] + fn test_auto_policy_large_pool() { + let models: Vec<&str> = vec![ + "gpt-4o", + "gpt-4o-mini", + "gpt-3.5-turbo", + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + "gemini-2.0-flash", + "deepseek-chat", + "unknown-model-1", + "unknown-model-2", + "unknown-model-3", + "unknown-model-4", + "unknown-model-5", + "unknown-model-6", + "unknown-model-7", + "unknown-model-8", + "unknown-model-9", + "unknown-model-10", + "unknown-model-11", + "unknown-model-12", + "unknown-model-13", + ]; + let policy = auto_policy(&models); + assert!(!policy.small.is_empty()); + assert!(!policy.medium.is_empty()); + assert!(!policy.large.is_empty()); + assert!(policy.allow_large); + assert_eq!(policy.max_cost_tier, CostTier::High); + } + + // ── classify_round_complexity tests ──────────────────────────────── + + #[test] + fn test_classify_round_complexity_round_0() { + let analysis = classify_round_complexity("click button", 1000, 0, false); + assert!( + analysis.requires_reasoning, + "round 0 always requires reasoning" + ); + assert!(analysis.requires_structured_output); + } + + #[test] + fn test_classify_round_complexity_stagnated() { + let analysis = classify_round_complexity("click button", 1000, 5, true); + assert!( + analysis.requires_reasoning, + "stagnated rounds need reasoning" + ); + assert!(analysis.multi_step, "stagnated rounds are multi-step"); + } + + #[test] + fn test_classify_round_complexity_large_html() { + let analysis = classify_round_complexity("click button", 60_000, 3, false); + assert!(analysis.requires_reasoning, "large HTML needs reasoning"); + } + + #[test] + fn test_classify_round_complexity_simple() { + let analysis = classify_round_complexity("click button", 1000, 3, false); + // Simple round: not round 0, not stagnated, small HTML, no reasoning keywords + assert!(!analysis.requires_reasoning); + assert!(!analysis.multi_step); + assert!(analysis.requires_structured_output); + } + + #[test] + fn test_policy_getter() { + let policy = ModelPolicy { + small: "small-model".to_string(), + medium: "medium-model".to_string(), + large: "large-model".to_string(), + allow_large: true, + max_latency_ms: None, + max_cost_tier: CostTier::High, + }; + let router = ModelRouter::with_policy(policy.clone()); + assert_eq!(router.policy().small, "small-model"); + assert_eq!(router.policy().large, "large-model"); + } +} diff --git a/spider_agent/src/automation/skills.rs b/spider_agent/src/automation/skills.rs new file mode 100644 index 000000000..9c0d49bf6 --- /dev/null +++ b/spider_agent/src/automation/skills.rs @@ -0,0 +1,813 @@ +//! Dynamic skill loading for web automation. +//! +//! Core types (`Skill`, `SkillTrigger`, `SkillRegistry`) are re-exported from +//! [`spider_skills`]. This module adds spider_agent-specific built-in skills +//! (with `pre_evaluate` JS) and S3 loading. +//! +//! # Example +//! ```rust +//! use spider_agent::automation::skills::{SkillRegistry, Skill, SkillTrigger}; +//! +//! let mut registry = SkillRegistry::new(); +//! +//! // Add a custom skill +//! registry.add(Skill::new( +//! "grid-selection", +//! "Image grid selection challenge solver", +//! ) +//! .with_trigger(SkillTrigger::title_contains("select all")) +//! .with_trigger(SkillTrigger::html_contains("grid-item")) +//! .with_content("For image grids, identify all matching tiles and click them...")); +//! +//! // Match skills against current page state +//! let context = registry.match_context("https://example.com", "Select all stop signs", "
..."); +//! // context contains the skill prompt to inject into system_prompt_extra +//! ``` + +// Re-export core types from spider_skills +pub use spider_skills::{Skill, SkillRegistry, SkillTrigger}; + +// ─── S3 skill loading types ──────────────────────────────────────────────── + +/// Configuration for loading skills from an S3-compatible bucket. +#[cfg(feature = "skills_s3")] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct S3SkillSource { + /// S3 bucket name. + pub bucket: String, + /// Folder prefix within the bucket, e.g. "tactics/". + #[serde(default)] + pub prefix: String, + /// AWS region override. Defaults to SDK environment resolution. + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option, + /// Custom endpoint URL for S3-compatible stores (MinIO, R2, etc.). + #[serde(skip_serializing_if = "Option::is_none")] + pub endpoint_url: Option, + /// File extensions to load. Defaults to `["md"]`. + #[serde(default = "default_s3_extensions")] + pub extensions: Vec, +} + +#[cfg(feature = "skills_s3")] +fn default_s3_extensions() -> Vec { + vec!["md".to_string()] +} + +#[cfg(feature = "skills_s3")] +impl S3SkillSource { + /// Create a new S3 skill source with bucket and prefix. + pub fn new(bucket: impl Into, prefix: impl Into) -> Self { + Self { + bucket: bucket.into(), + prefix: prefix.into(), + region: None, + endpoint_url: None, + extensions: default_s3_extensions(), + } + } + + /// Set the AWS region. + pub fn with_region(mut self, region: impl Into) -> Self { + self.region = Some(region.into()); + self + } + + /// Set a custom endpoint URL (for MinIO, R2, etc.). + pub fn with_endpoint_url(mut self, url: impl Into) -> Self { + self.endpoint_url = Some(url.into()); + self + } + + /// Set file extensions to load (e.g. `["md", "json"]`). + pub fn with_extensions(mut self, exts: Vec) -> Self { + self.extensions = exts; + self + } +} + +/// Errors from S3 skill loading. +#[cfg(feature = "skills_s3")] +#[derive(Debug)] +pub enum S3SkillError { + /// AWS SDK error. + Aws(String), + /// No skills found in the specified bucket/prefix. + NoSkillsFound, + /// Failed to parse a skill file. + ParseError { + /// S3 object key that failed to parse. + key: String, + /// Reason for the parse failure. + reason: String, + }, +} + +#[cfg(feature = "skills_s3")] +impl std::fmt::Display for S3SkillError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Aws(e) => write!(f, "AWS S3 error: {}", e), + Self::NoSkillsFound => write!(f, "no skills found in S3 bucket"), + Self::ParseError { key, reason } => { + write!(f, "failed to parse skill '{}': {}", key, reason) + } + } + } +} + +#[cfg(feature = "skills_s3")] +impl std::error::Error for S3SkillError {} + +// ─── Built-in web challenge skills ────────────────────────────────────────── + +/// Create a registry pre-loaded with built-in web challenge skills. +/// +/// Loads all skills from `spider_skills::web_challenges::registry()` and then +/// adds engine-specific `pre_evaluate` JavaScript for skills that need it. +/// Pre-evaluate JS runs in the browser before the LLM sees the page, +/// extracting puzzle state automatically. +pub fn builtin_web_challenges() -> SkillRegistry { + // Load all skills from the spider_skills crate (110 skills) + let mut registry = spider_skills::web_challenges::registry(); + + // Add pre_evaluate JS overlays for skills that need engine-side computation. + // These run before the LLM inference round to extract puzzle state into document.title. + + if let Some(skill) = registry.get_mut("rotation-puzzle") { + skill.pre_evaluate = Some(ROTATION_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("tic-tac-toe") { + skill.pre_evaluate = Some(TTT_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("word-search") { + skill.pre_evaluate = Some(WORD_SEARCH_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("nested-grid") { + skill.pre_evaluate = Some(NESTED_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("whack-a-mole") { + skill.pre_evaluate = Some(WHACK_A_MOLE_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("draw-circle") { + skill.pre_evaluate = Some(DRAW_CIRCLE_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("sliding-puzzle") { + skill.pre_evaluate = Some(SLIDING_PUZZLE_PRE_EVALUATE_JS.to_string()); + } + + if let Some(skill) = registry.get_mut("math-solver") { + skill.pre_evaluate = Some(MATH_PRE_EVALUATE_JS.to_string()); + } + + registry +} + +// ─── S3 skill loading ─────────────────────────────────────────────────────── + +/// Load skills from an S3-compatible bucket into an existing registry. +/// +/// Name conflicts: S3 skills replace any existing skill with the same name. +/// Returns the count of successfully loaded skills. +#[cfg(feature = "skills_s3")] +pub async fn load_from_s3( + registry: &mut SkillRegistry, + source: &S3SkillSource, +) -> Result { + let sdk_config = { + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()); + if let Some(ref region) = source.region { + loader = loader.region(aws_config::Region::new(region.clone())); + } + if let Some(ref endpoint) = source.endpoint_url { + loader = loader.endpoint_url(endpoint); + } + loader.load().await + }; + + let client = aws_sdk_s3::Client::new(&sdk_config); + + let mut continuation_token: Option = None; + let mut loaded = 0usize; + let exts: Vec<&str> = source.extensions.iter().map(|s| s.as_str()).collect(); + + loop { + let mut req = client + .list_objects_v2() + .bucket(&source.bucket) + .prefix(&source.prefix); + + if let Some(ref token) = continuation_token { + req = req.continuation_token(token); + } + + let resp = req + .send() + .await + .map_err(|e| S3SkillError::Aws(e.to_string()))?; + + let contents = resp.contents(); + { + for obj in contents { + let key = match obj.key() { + Some(k) => k, + None => continue, + }; + + // Filter by extension + let matches_ext = exts.iter().any(|ext| key.ends_with(&format!(".{}", ext))); + if !matches_ext { + continue; + } + + // Download the object + let get_resp = client + .get_object() + .bucket(&source.bucket) + .key(key) + .send() + .await + .map_err(|e| S3SkillError::Aws(e.to_string()))?; + + let body = get_resp + .body + .collect() + .await + .map_err(|e| S3SkillError::Aws(e.to_string()))?; + + let text = String::from_utf8_lossy(&body.into_bytes()).into_owned(); + + // Parse based on extension + let skill = if key.ends_with(".json") { + serde_json::from_str::(&text).map_err(|e| S3SkillError::ParseError { + key: key.to_string(), + reason: e.to_string(), + })? + } else { + // Markdown + Skill::from_markdown(&text).ok_or_else(|| S3SkillError::ParseError { + key: key.to_string(), + reason: "invalid markdown frontmatter".to_string(), + })? + }; + + // Replace existing skill with same name + registry.remove(&skill.name); + registry.add(skill); + loaded += 1; + } + } + + if resp.is_truncated() == Some(true) { + continuation_token = resp.next_continuation_token().map(|s| s.to_string()); + } else { + break; + } + } + + if loaded == 0 { + return Err(S3SkillError::NoSkillsFound); + } + + Ok(loaded) +} + +/// Create a registry with built-in web challenge skills merged with S3 skills. +/// +/// S3 skills override built-in skills with the same name. +#[cfg(feature = "skills_s3")] +pub async fn with_builtin_and_s3(source: &S3SkillSource) -> Result { + let mut registry = builtin_web_challenges(); + load_from_s3(&mut registry, source).await?; + Ok(registry) +} + +// ─── Pre-evaluate JS constants ────────────────────────────────────────── +// +// These JS snippets run in the browser BEFORE the LLM sees the page. +// They extract puzzle state into document.title so the model can read it. +// Skill content lives in spider_skills crate (.md files via include_str!). + +/// JS executed by the engine before the LLM sees the rotation page. +/// Reads each tile's CSS transform, computes clicks needed, writes to title. +/// Does NOT click — the model uses real Click actions based on the title info. +const ROTATION_PRE_EVALUATE_JS: &str = "try{const t=[...document.querySelectorAll('.rotating-item')];const n=t.length;const tiles=t.map((e,i)=>{const m=getComputedStyle(e).transform;let c=0;if(m&&m!=='none'){const v=m.match(/matrix\\(([^)]+)\\)/);if(v){const p=v[1].split(',').map(Number);const a=Math.round(Math.atan2(p[1],p[0])*180/Math.PI);c=a>45&&a<135?3:Math.abs(a)>135?2:a<-45&&a>-135?1:0;}}return{i,c};});const done=tiles.every(t=>t.c===0);const clicks=tiles.filter(t=>t.c>0).map(t=>t.i+':'+t.c).join(',');document.title='ROT:'+JSON.stringify({n,done,clicks});}catch(e){document.title='ROT_ERR:'+e.message;}"; + +/// JS executed by the engine before the LLM sees the TTT page. +/// Strategy: win → block → fork (create 2+ threats) → block fork → center → opposite corner → corner → side. +const TTT_PRE_EVALUATE_JS: &str = r##"try{const cells=[...document.querySelectorAll('.grid-item')].filter(el=>el.offsetWidth>20&&el.offsetHeight>20);const n=cells.length;const occ=new Set();cells.forEach((el,i)=>{const inner=el.querySelector('.tic-tac-toe-cell');if(inner&&(inner.className.includes('cell-selected')||inner.className.includes('cell-disabled')))occ.add(i);});let tr=document.getElementById('ttt-h');if(!tr){tr=document.createElement('div');tr.id='ttt-h';tr.style.display='none';tr.dataset.m='';document.body.appendChild(tr);}const my=new Set(tr.dataset.m?tr.dataset.m.split(',').map(Number):[]);if(occ.size===0)my.clear();for(const m of my)if(!occ.has(m))my.delete(m);const opp=new Set([...occ].filter(i=>!my.has(i)));const board=Array.from({length:9},(_,i)=>my.has(i)?'M':opp.has(i)?'T':'.');const W=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];let myWin=false,thWin=false;for(const w of W){if(w.every(i=>board[i]==='M'))myWin=true;if(w.every(i=>board[i]==='T'))thWin=true;} +function countThreats(b,mark){let t=0;for(const w of W){const m=w.filter(i=>b[i]===mark),e=w.filter(i=>b[i]==='.');if(m.length===2&&e.length===1)t++;}return t;} +let best=-1;if(!myWin&&!thWin){ +for(const w of W){const mi=w.filter(i=>board[i]==='M'),e=w.filter(i=>board[i]==='.');if(mi.length===2&&e.length===1){best=e[0];break;}} +if(best<0)for(const w of W){const ti=w.filter(i=>board[i]==='T'),e=w.filter(i=>board[i]==='.');if(ti.length===2&&e.length===1){best=e[0];break;}} +if(best<0){let bestFork=-1,bestScore=0;for(let i=0;i<9;i++){if(board[i]!=='.')continue;const b2=[...board];b2[i]='M';const threats=countThreats(b2,'M');if(threats>=2&&threats>bestScore){bestScore=threats;bestFork=i;}}best=bestFork;} +if(best<0){for(let i=0;i<9;i++){if(board[i]!=='.')continue;const b2=[...board];b2[i]='T';if(countThreats(b2,'T')>=2){best=i;break;}}} +if(best<0&&board[4]==='.')best=4; +if(best<0){const opc=[[0,8],[2,6],[6,2],[8,0]];for(const[c,o]of opc)if(board[c]==='T'&&board[o]==='.')best=o;} +if(best<0)for(const c of[0,2,6,8])if(board[c]==='.'){best=c;break;} +if(best<0)for(const c of[1,3,5,7])if(board[c]==='.'){best=c;break;} +}let clicked=false;if(best>=0&&cells[best]){const el=cells[best];const r=el.getBoundingClientRect();const ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2};el.dispatchEvent(new PointerEvent('pointerdown',ev));el.dispatchEvent(new MouseEvent('mousedown',ev));el.dispatchEvent(new PointerEvent('pointerup',ev));el.dispatchEvent(new MouseEvent('mouseup',ev));el.dispatchEvent(new MouseEvent('click',ev));clicked=true;my.add(best);board[best]='M';for(const w of W)if(w.every(i=>board[i]==='M'))myWin=true;}tr.dataset.m=[...my].join(',');const full=!board.includes('.');document.title='TTT:'+JSON.stringify({n,board:board.join(''),best,clicked,myWin,thWin,full});}catch(e){document.title='TTT_ERR:'+e.message;} +"##; + +/// JS executed by the engine before the LLM sees the Word Search page. +/// Extracts grid, finds words, outputs coordinates for engine-side CDP drag. +const WORD_SEARCH_PRE_EVALUATE_JS: &str = r##"try{ +const doneEl=document.getElementById('ws-engine-done'); +if(doneEl){document.title=doneEl.dataset.t;throw'done';} +let cells=[...document.querySelectorAll('.word-search-grid-item')]; +if(!cells.length)cells=[...document.querySelectorAll('.grid-item')].filter(el=>el.textContent.trim().length===1); +if(!cells.length)cells=[...document.querySelectorAll('[class*=letter]')].filter(el=>el.textContent.trim().length===1); +const n=cells.length; +if(n<4){document.title='WS:'+JSON.stringify({n,err:'no_grid'});} +else{ +const rects=cells.map(c=>{const r=c.getBoundingClientRect();return{x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2)};}); +const letters=cells.map(c=>c.textContent.trim().toUpperCase()); +const tops=[...new Set(rects.map(r=>r.y))].sort((a,b)=>a-b); +const rows=tops.length||1,cols=Math.round(n/rows)||1; +const grid=[];for(let r=0;r=rows||nc<0||nc>=cols||grid[nr][nc]!==w[k]){ok=false;break;}}if(ok){const pts=[];for(let k=0;kel.textContent.trim().toUpperCase().replace(/\s+/g,'')).filter(w=>w.length>1&&w.length<=20&&w.match(/^[A-Z]+$/)); +if(!words.length){ + const txts=[...document.querySelectorAll('h1,h2,h3,h4,p,span,div,label')]; + for(const el of txts){ + const t=el.textContent.trim(); + if(t.length>200||t.length<8)continue; + const m=t.match(/(?:select|find|choose|highlight)\s+(?:all\s+)?(?:the\s+)?(?:squares?\s+)?(?:with|containing|that\s+have|showing|of)\s+(?:a\s+|an?\s+)?(.+)/i); + if(m){ + const raw=m[1].replace(/[.!?]+$/,'').split(/\s+and\s+|,\s*/i).map(s=>s.trim()); + words=raw.map(s=>s.toUpperCase().replace(/[^A-Z]/g,'')).filter(w=>w.length>=2); + if(words.length)break; + } + } +} +const found={}; +words.forEach(w=>{ + const pts=findWord(w); + if(pts){found[w]=pts;return;} + for(let i=2;i<=w.length-2;i++){const p1=w.slice(0,i),p2=w.slice(i);const r1=findWord(p1),r2=findWord(p2);if(r1&&r2){found[p1]=r1;found[p2]=r2;return;}} +}); +const fk=Object.keys(found); +if(fk.length>0){document.title='WS_DRAG:'+JSON.stringify({n,words:fk,drags:fk.map(w=>found[w])});} +else{const gt=grid.map(r=>r.join('')).join('/');document.title='WS:'+JSON.stringify({n,rows,cols,grid:gt,words,found:{}});} +}}catch(e){if(e!=='done'){document.title='WS_ERR:'+(e&&e.message||String(e));}} +"##; + +/// JS executed by the engine before the LLM sees the nested grid page. +/// Detects stop sign image, computes bounding-rect overlap with leaf boxes, +/// and reports JSON with toClick array for engine auto-clicking. +const NESTED_PRE_EVALUATE_JS: &str = r##"try{ +var done=document.getElementById('nest-engine-done'); +if(done){document.title=done.dataset.t;throw'done';} +var all=[...document.querySelectorAll('.box')]; +var leaf=all.filter(function(b){return !b.querySelector('.box')&&!b.querySelector('.nested-container');}); +leaf.forEach(function(b,i){b.setAttribute('data-spider-id',String(i));}); +var ssRect=null; +var imgs=[...document.querySelectorAll('img')].filter(function(el){var r=el.getBoundingClientRect();return r.width>20&&r.height>20;}); +if(imgs.length>0){var best=imgs[0];for(var im of imgs){var rr=im.getBoundingClientRect();if(rr.width*rr.height>best.getBoundingClientRect().width*best.getBoundingClientRect().height)best=im;}ssRect=best.getBoundingClientRect();} +if(!ssRect){var cands=[...document.querySelectorAll('[class*=sign],[class*=stop],[class*=image],[class*=captcha-image]')].filter(function(el){var r=el.getBoundingClientRect();return r.width>20&&r.height>20;});if(cands.length>0)ssRect=cands[0].getBoundingClientRect();} +if(!ssRect){var bgEls=[...document.querySelectorAll('.nested-container,div')].filter(function(el){var bg=getComputedStyle(el).backgroundImage;return bg&&bg!=='none';});if(bgEls.length>0)ssRect=bgEls[0].getBoundingClientRect();} +var toClick=[];var selected=[]; +var boxes=leaf.map(function(b,i){var r=b.getBoundingClientRect();var sel=b.classList.contains('selected');if(sel){selected.push(i);}else if(ssRect){var ox=Math.max(0,Math.min(r.right,ssRect.right)-Math.max(r.left,ssRect.left));var oy=Math.max(0,Math.min(r.bottom,ssRect.bottom)-Math.max(r.top,ssRect.top));if(ox>2&&oy>2)toClick.push(i);} +return{id:i,x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2),w:Math.round(r.width),h:Math.round(r.height)};}); +document.title='NEST:'+JSON.stringify({total:leaf.length,sel:selected.length,toClick:toClick,selected:selected,hasSign:!!ssRect,boxes:boxes}); +}catch(e){if(e!=='done')document.title='NEST_ERR:'+e.message;} +"##; + +/// JS executed by the engine for whack-a-mole — detects and clicks visible moles. +const WHACK_A_MOLE_PRE_EVALUATE_JS: &str = r##"try{ +var done=document.getElementById('wam-engine-done'); +if(done){document.title=done.dataset.t;throw'done';} +var moles=[...document.querySelectorAll('.grid-item,.mole,.whack-target,[class*=mole]')].filter(el=>{ + var r=el.getBoundingClientRect(); + if(r.width<10||r.height<10)return false; + var s=getComputedStyle(el); + if(s.display==='none'||s.visibility==='hidden'||s.opacity==='0')return false; + var img=el.querySelector('img'); + if(img){var src=img.src||img.dataset.src||'';if(src.includes('mole')||src.includes('gopher'))return true;} + var bg=s.backgroundImage||''; + if(bg.includes('mole')||bg.includes('gopher'))return true; + var cls=(el.className||'').toLowerCase(); + if(cls.includes('mole')||cls.includes('active')||cls.includes('visible'))return true; + return false; +}); +var clicked=0; +moles.forEach(function(el){ + var r=el.getBoundingClientRect(); + var ev={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2}; + el.dispatchEvent(new PointerEvent('pointerdown',ev)); + el.dispatchEvent(new MouseEvent('mousedown',ev)); + el.dispatchEvent(new PointerEvent('pointerup',ev)); + el.dispatchEvent(new MouseEvent('mouseup',ev)); + el.dispatchEvent(new MouseEvent('click',ev)); + clicked++; +}); +document.title='WAM:'+JSON.stringify({moles:moles.length,clicked:clicked}); +}catch(e){if(e!=='done')document.title='WAM_ERR:'+(e&&e.message||String(e));} +"##; + +/// JS that computes a circular mouse path and reports center/radius for engine drag. +const DRAW_CIRCLE_PRE_EVALUATE_JS: &str = r##"try{ +var done=document.getElementById('circle-engine-done'); +if(done){document.title=done.dataset.t;throw'done';} +var canvas=document.querySelector('canvas'); +if(!canvas){var divs=[...document.querySelectorAll('div')].filter(function(d){var r=d.getBoundingClientRect();return r.width>100&&r.height>100&&r.width<800;});canvas=divs[0];} +if(canvas){var r=canvas.getBoundingClientRect();var cx=Math.round(r.x+r.width/2);var cy=Math.round(r.y+r.height/2);var rad=Math.round(Math.min(r.width,r.height)*0.35);var pts=[];for(var i=0;i<=36;i++){var a=i*Math.PI*2/36;pts.push({x:Math.round(cx+rad*Math.cos(a)),y:Math.round(cy+rad*Math.sin(a))});} +document.title='CIRCLE:'+JSON.stringify({cx:cx,cy:cy,rad:rad,pts:pts});} +else{document.title='CIRCLE_ERR:no_canvas';} +}catch(e){if(e!=='done')document.title='CIRCLE_ERR:'+e.message;} +"##; + +/// JS that reads sliding puzzle tile positions and computes moves. +const SLIDING_PUZZLE_PRE_EVALUATE_JS: &str = r##"try{ +var tiles=[...document.querySelectorAll('[class*=tile],[class*=slide],[class*=puzzle] > div')].filter(function(t){var r=t.getBoundingClientRect();return r.width>20&&r.height>20;}); +if(tiles.length>0){var info=tiles.map(function(t,i){var r=t.getBoundingClientRect();var txt=(t.textContent||'').trim().substring(0,5);return{id:i,x:Math.round(r.x),y:Math.round(r.y),w:Math.round(r.width),h:Math.round(r.height),txt:txt,cls:t.className.substring(0,30)};}); +document.title='SLIDE:'+JSON.stringify({n:tiles.length,tiles:info});} +else{document.title='SLIDE_ERR:no_tiles';} +}catch(e){document.title='SLIDE_ERR:'+e.message;} +"##; + +/// JS that reads a math expression from the page and computes the answer. +const MATH_PRE_EVALUATE_JS: &str = r##"try{ +var expr='';var els=[...document.querySelectorAll('h1,h2,h3,.math,.equation,.captcha-text,.challenge-text,p,span')]; +for(var el of els){var t=(el.textContent||'').trim();if(/[\d\+\-\*\/\=\^]+/.test(t)&&t.length>2&&t.length<100){expr=t;break;}} +if(expr){try{var clean=expr.replace(/[^0-9\+\-\*\/\.\(\)\^%]/g,' ').trim().replace(/\^/g,'**');var ans=Function('"use strict";return ('+clean+')')();document.title='MATH:'+JSON.stringify({expr:expr,answer:String(ans)});}catch(ee){document.title='MATH:'+JSON.stringify({expr:expr,answer:''});}} +else{document.title='MATH_ERR:no_expression';} +}catch(e){document.title='MATH_ERR:'+e.message;} +"##; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_skill_trigger_matching() { + let skill = Skill::new("grid-selection", "Select images from grid") + .with_trigger(SkillTrigger::title_contains("select all")) + .with_trigger(SkillTrigger::html_contains("grid-item")) + .with_content("Select tiles matching the description..."); + + // Title match + assert!(skill.matches("https://example.com", "Select all stop signs", "")); + // HTML match + assert!(skill.matches("https://example.com", "", "
")); + // No match + assert!(!skill.matches("https://example.com", "Home page", "
hello
")); + } + + #[test] + fn test_skill_registry_matching() { + let mut registry = SkillRegistry::new(); + + registry.add( + Skill::new("grid", "Grid challenges") + .with_trigger(SkillTrigger::html_contains("grid-item")) + .with_content("Grid strategy here") + .with_priority(1), + ); + + registry.add( + Skill::new("ttt", "Tic-tac-toe") + .with_trigger(SkillTrigger::title_contains("xoxo")) + .with_content("TTT strategy here") + .with_priority(2), + ); + + // Only grid matches + let ctx = registry.match_context("", "", "
"); + assert!(ctx.contains("Grid strategy")); + assert!(!ctx.contains("TTT strategy")); + + // Only TTT matches + let ctx = registry.match_context("", "XOXO Game", "
board
"); + assert!(ctx.contains("TTT strategy")); + assert!(!ctx.contains("Grid strategy")); + + // Neither matches + let ctx = registry.match_context("", "Home", "
hello
"); + assert!(ctx.is_empty()); + } + + #[test] + fn test_skill_from_markdown() { + let md = r#"--- +name: word-search +description: Word search grid solver +priority: 5 +triggers: + - title_contains: word search + - html_contains: grid-item +--- + +# Word Search Strategy + +Find words in the grid by extracting letters and searching algorithmically. +Use Evaluate to click found cells programmatically."#; + + let skill = Skill::from_markdown(md).unwrap(); + assert_eq!(skill.name, "word-search"); + assert_eq!(skill.description, "Word search grid solver"); + assert_eq!(skill.priority, 5); + assert_eq!(skill.triggers.len(), 2); + assert!(skill.content.contains("Word Search Strategy")); + assert!(skill.content.contains("algorithmically")); + } + + #[test] + fn test_skill_no_triggers_never_matches() { + let skill = Skill::new("empty", "No triggers").with_content("content"); + assert!(!skill.matches("", "", "")); + } + + #[test] + fn test_always_trigger() { + let skill = Skill::new("always", "Always active") + .with_trigger(SkillTrigger::Always) + .with_content("Always injected"); + + assert!(skill.matches("", "", "")); + assert!(skill.matches("any", "any", "any")); + } + + #[test] + fn test_builtin_web_challenges() { + let registry = builtin_web_challenges(); + assert!(registry.len() >= 110); + + // Image grid should match on grid-item class + let ctx = registry.match_context("", "", "
img
"); + assert!(ctx.contains("image-grid-selection")); + + // Tic-tac-toe should match on XOXO title + let ctx = registry.match_context("", "XOXO Game", ""); + assert!(ctx.contains("tic-tac-toe")); + + // Word search should match on title + let ctx = registry.match_context("", "Word Search Puzzle", ""); + assert!(ctx.contains("word-search")); + + // Rotation should match on rotating-item class + let ctx = registry.match_context("", "", "
"); + assert!(ctx.contains("rotation-puzzle")); + + // No match on unrelated page + let ctx = registry.match_context("https://example.com", "Home", "
hello
"); + assert!(ctx.is_empty()); + } + + #[test] + fn test_pre_evaluate_field() { + // Default is None + let skill = Skill::new("test", "test skill"); + assert!(skill.pre_evaluate.is_none()); + + // Builder sets it + let skill = Skill::new("test", "test skill").with_pre_evaluate("document.title='HELLO'"); + assert_eq!( + skill.pre_evaluate.as_deref(), + Some("document.title='HELLO'") + ); + + // from_markdown leaves it None + let md = "---\nname: foo\n---\ncontent"; + let skill = Skill::from_markdown(md).unwrap(); + assert!(skill.pre_evaluate.is_none()); + } + + #[test] + fn test_find_pre_evaluates() { + let mut registry = SkillRegistry::new(); + + // Skill with pre_evaluate + registry.add( + Skill::new("solver", "Auto-solver") + .with_trigger(SkillTrigger::title_contains("game")) + .with_pre_evaluate("document.title='SOLVED'") + .with_content("Read the title.") + .with_priority(5), + ); + + // Skill without pre_evaluate + registry.add( + Skill::new("helper", "Manual helper") + .with_trigger(SkillTrigger::title_contains("game")) + .with_content("Use Evaluate to read...") + .with_priority(3), + ); + + // Both match, but only one has pre_evaluate + let pre_evals = registry.find_pre_evaluates("", "game level", ""); + assert_eq!(pre_evals.len(), 1); + assert_eq!(pre_evals[0].0, "solver"); + assert_eq!(pre_evals[0].1, "document.title='SOLVED'"); + + // No match + let pre_evals = registry.find_pre_evaluates("", "home", ""); + assert!(pre_evals.is_empty()); + } + + #[test] + fn test_builtin_ttt_has_pre_evaluate() { + let registry = builtin_web_challenges(); + let ttt = registry + .get("tic-tac-toe") + .expect("tic-tac-toe skill missing"); + assert!( + ttt.pre_evaluate.is_some(), + "TTT should have pre_evaluate JS" + ); + let js = ttt.pre_evaluate.as_deref().unwrap(); + assert!( + js.contains("TTT:"), + "TTT pre_evaluate should set title with TTT: prefix" + ); + assert!( + js.contains("cell-selected"), + "TTT pre_evaluate should use cell-selected selector" + ); + assert!( + js.contains("cell-disabled"), + "TTT pre_evaluate should use cell-disabled selector" + ); + assert!( + js.contains("TTT_ERR"), + "TTT pre_evaluate should have error handling" + ); + } + + #[test] + fn test_builtin_word_search_has_pre_evaluate() { + let registry = builtin_web_challenges(); + let ws = registry + .get("word-search") + .expect("word-search skill missing"); + assert!( + ws.pre_evaluate.is_some(), + "Word search should have pre_evaluate JS" + ); + let js = ws.pre_evaluate.as_deref().unwrap(); + assert!( + js.contains("WS:"), + "WS pre_evaluate should set title with WS: prefix" + ); + assert!( + js.contains("word-search-grid-item"), + "WS pre_evaluate should use word-search-grid-item selector" + ); + assert!( + js.contains("WS_ERR"), + "WS pre_evaluate should have error handling" + ); + } + + #[test] + fn test_builtin_ttt_triggers_fixed() { + let registry = builtin_web_challenges(); + let ttt = registry.get("tic-tac-toe").unwrap(); + + // Should match on cell-selected (the correct DOM class) + assert!(ttt.matches("", "", "
")); + // Should match on cell-disabled (the correct DOM class) + assert!(ttt.matches("", "", "
")); + // Should match on XOXO title + assert!(ttt.matches("", "XOXO", "")); + } + + #[test] + fn test_simplified_skills_no_evaluate_js() { + let registry = builtin_web_challenges(); + + // TTT simplified content should NOT contain Evaluate JS + let ttt = registry.get("tic-tac-toe").unwrap(); + assert!(ttt.content.contains("Do NOT write any Evaluate JS")); + assert!(!ttt.content.contains("querySelectorAll")); + + // Word search is self-solving via pre_evaluate + let ws = registry.get("word-search").unwrap(); + assert!( + ws.content.contains("auto-dragged"), + "WS skill should mention auto-drag" + ); + assert!(ws.pre_evaluate.is_some(), "WS should have pre_evaluate JS"); + + // Rotation simplified content should NOT contain Evaluate JS + let rot = registry.get("rotation-puzzle").unwrap(); + assert!(rot.content.contains("Do NOT write any Evaluate JS")); + assert!(!rot.content.contains("querySelectorAll")); + assert!( + rot.pre_evaluate.is_some(), + "Rotation should have pre_evaluate JS" + ); + + assert!( + ws.content.contains("Do NOT write any drag"), + "WS skill should prohibit manual drag JS" + ); + } + + #[test] + fn test_skills_without_pre_evaluate_unchanged() { + let registry = builtin_web_challenges(); + + // These skills should NOT have pre_evaluate (content-only skills) + for name in &[ + "image-grid-selection", + "text-captcha", + "slider-drag", + "checkbox-click", + "license-plate", + "find-waldo", + "chihuahua-muffin", + "reverse-selection", + "affirmations", + ] { + let skill = registry + .get(name) + .unwrap_or_else(|| panic!("{} missing", name)); + assert!( + skill.pre_evaluate.is_none(), + "{} should NOT have pre_evaluate", + name + ); + } + } + + #[test] + fn test_new_level_skills_present() { + let registry = builtin_web_challenges(); + + // L8: License plate + let lp = registry + .get("license-plate") + .expect("license-plate missing"); + assert!(lp.matches("", "License Plate Challenge", "")); + assert!(lp.content.contains("license plate")); + + // L9: Nested grid + let nested = registry.get("nested-grid").expect("nested-grid missing"); + assert!(nested.matches("", "Nested Squares", "")); + assert!(nested.content.contains("SUBDIVIDE")); + assert!( + nested.priority > 5, + "nested-grid should override image-grid" + ); + + // L10: Whack-a-mole + let wam = registry.get("whack-a-mole").expect("whack-a-mole missing"); + assert!(wam.matches("", "Whack a Mole!", "")); + assert!(wam.pre_evaluate.is_some(), "WAM should have pre_evaluate"); + let js = wam.pre_evaluate.as_deref().unwrap(); + assert!( + js.contains("WAM:"), + "WAM pre_evaluate should set WAM: prefix" + ); + + // L11: Waldo + let waldo = registry.get("find-waldo").expect("find-waldo missing"); + assert!(waldo.matches("", "Where's Waldo", "")); + assert!(waldo.content.contains("striped")); + + // L12: Chihuahua vs muffin + let cm = registry + .get("chihuahua-muffin") + .expect("chihuahua-muffin missing"); + assert!(cm.matches("", "Muffins? Or Chihuahuas", "")); + assert!(cm.content.contains("CHIHUAHUAS")); + + // L13: Reverse selection + let rev = registry + .get("reverse-selection") + .expect("reverse-selection missing"); + assert!(rev.matches("", "Reverse CAPTCHA", "")); + assert!(rev.content.contains("OPPOSITE")); + + // L14: Affirmations + let aff = registry.get("affirmations").expect("affirmations missing"); + assert!(aff.matches("", "Affirmations Level", "")); + assert!(aff.content.contains("I'm not a robot")); + } + + #[test] + fn test_nested_overrides_image_grid() { + let registry = builtin_web_challenges(); + let nested = registry.get("nested-grid").unwrap(); + let img_grid = registry.get("image-grid-selection").unwrap(); + assert!( + nested.priority > img_grid.priority, + "nested-grid priority ({}) should be higher than image-grid ({})", + nested.priority, + img_grid.priority + ); + } +} diff --git a/spider_agent/src/browser.rs b/spider_agent/src/browser.rs new file mode 100644 index 000000000..e76e7cae1 --- /dev/null +++ b/spider_agent/src/browser.rs @@ -0,0 +1,500 @@ +//! Browser automation module for spider_agent. +//! +//! Provides Chrome page management with support for: +//! - Page cloning +//! - Opening new pages/tabs +//! - Screenshot capture +//! - Navigation and interaction + +use std::sync::Arc; + +// Re-export chromey types +pub use chromiumoxide::browser::Browser; +pub use chromiumoxide::error::CdpError; +pub use chromiumoxide::page::Page; + +/// Browser context for managing Chrome pages. +/// +/// Wraps a chromey Page with additional utilities for agent operations. +#[derive(Clone)] +pub struct BrowserContext { + /// The browser instance. + browser: Arc, + /// The current page. + page: Arc, +} + +impl BrowserContext { + /// Create a new browser context from an existing browser and page. + pub fn new(browser: Arc, page: Arc) -> Self { + Self { browser, page } + } + + /// Get the current page. + pub fn page(&self) -> &Arc { + &self.page + } + + /// Get the browser instance. + pub fn browser(&self) -> &Arc { + &self.browser + } + + /// Open a new page/tab in the browser. + pub async fn new_page(&self) -> Result, CdpError> { + let page = self.browser.new_page("about:blank").await?; + Ok(Arc::new(page)) + } + + /// Open a new page and navigate to URL. + pub async fn new_page_with_url(&self, url: &str) -> Result, CdpError> { + let page = self.browser.new_page(url).await?; + Ok(Arc::new(page)) + } + + /// Clone the current page context (opens a new page with same URL). + pub async fn clone_page(&self) -> Result { + let url = self + .page + .url() + .await? + .unwrap_or_else(|| "about:blank".to_string()); + let new_page = self.browser.new_page(&url).await?; + Ok(BrowserContext { + browser: self.browser.clone(), + page: Arc::new(new_page), + }) + } + + /// Navigate to a URL. + pub async fn navigate(&self, url: &str) -> Result<(), CdpError> { + self.page.goto(url).await?; + Ok(()) + } + + /// Get the current URL. + pub async fn url(&self) -> Result, CdpError> { + self.page.url().await + } + + /// Get the page HTML content. + pub async fn html(&self) -> Result { + self.page.content().await + } + + /// Take a screenshot and return PNG bytes. + pub async fn screenshot(&self) -> Result, CdpError> { + self.page + .screenshot( + chromiumoxide::page::ScreenshotParams::builder() + .full_page(true) + .build(), + ) + .await + } + + /// Take a screenshot of the visible viewport. + pub async fn screenshot_viewport(&self) -> Result, CdpError> { + self.page + .screenshot( + chromiumoxide::page::ScreenshotParams::builder() + .full_page(false) + .build(), + ) + .await + } + + /// Click an element by selector. + pub async fn click(&self, selector: &str) -> Result<(), CdpError> { + let element = self.page.find_element(selector).await?; + element.click().await?; + Ok(()) + } + + /// Click all elements matching a selector. + /// Returns the number of elements clicked. + pub async fn click_all(&self, selector: &str) -> Result { + let elements = self.page.find_elements(selector).await?; + let count = elements.len(); + for element in elements { + let _ = element.click().await; + } + Ok(count) + } + + /// Click at specific x,y coordinates with smooth human-like mouse movement. + pub async fn click_point(&self, x: f64, y: f64) -> Result<(), CdpError> { + use chromiumoxide::layout::Point; + self.page.click_smooth(Point::new(x, y)).await?; + Ok(()) + } + + /// Click and hold on an element with smooth mouse movement. + pub async fn click_hold(&self, selector: &str, hold_ms: u64) -> Result<(), CdpError> { + let element = self.page.find_element(selector).await?; + let point = element.clickable_point().await?; + self.page.move_mouse_smooth(point).await?; + self.page + .click_and_hold(point, std::time::Duration::from_millis(hold_ms)) + .await?; + Ok(()) + } + + /// Click and hold at a specific point with smooth mouse movement. + pub async fn click_hold_point(&self, x: f64, y: f64, hold_ms: u64) -> Result<(), CdpError> { + use chromiumoxide::layout::Point; + let point = Point::new(x, y); + self.page.move_mouse_smooth(point).await?; + self.page + .click_and_hold(point, std::time::Duration::from_millis(hold_ms)) + .await?; + Ok(()) + } + + /// Click and drag from one element to another. + pub async fn click_drag( + &self, + from_selector: &str, + to_selector: &str, + modifier: Option, + ) -> Result<(), CdpError> { + let from_elem = self.page.find_element(from_selector).await?; + let to_elem = self.page.find_element(to_selector).await?; + + let from_point = from_elem.clickable_point().await?; + let to_point = to_elem.clickable_point().await?; + + self.click_drag_point( + (from_point.x, from_point.y), + (to_point.x, to_point.y), + modifier, + ) + .await + } + + /// Click and drag from one point to another with smooth bezier movement. + pub async fn click_drag_point( + &self, + from: (f64, f64), + to: (f64, f64), + modifier: Option, + ) -> Result<(), CdpError> { + use chromiumoxide::layout::Point; + let from_point = Point::new(from.0, from.1); + let to_point = Point::new(to.0, to.1); + match modifier { + Some(m) => { + self.page + .click_and_drag_smooth_with_modifier(from_point, to_point, m) + .await? + } + None => { + self.page + .click_and_drag_smooth(from_point, to_point) + .await? + } + }; + Ok(()) + } + + /// Click all clickable elements on the page. + pub async fn click_all_clickable(&self) -> Result { + // Find common clickable elements + let script = r#" + Array.from(document.querySelectorAll('a, button, [onclick], [role="button"], input[type="submit"], input[type="button"]')) + .filter(el => { + const style = window.getComputedStyle(el); + return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetParent !== null; + }) + .length + "#; + + let count: usize = self + .page + .evaluate(script) + .await? + .into_value() + .map_err(|e| { + CdpError::ChromeMessage(format!("Failed to count clickable elements: {}", e)) + })?; + + // Click each one (with error handling) + let click_script = r#" + const elements = Array.from(document.querySelectorAll('a, button, [onclick], [role="button"], input[type="submit"], input[type="button"]')) + .filter(el => { + const style = window.getComputedStyle(el); + return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetParent !== null; + }); + elements.forEach(el => { try { el.click(); } catch(e) {} }); + elements.length + "#; + + let clicked: usize = self + .page + .evaluate(click_script) + .await? + .into_value() + .unwrap_or(0); + + Ok(clicked.min(count)) + } + + /// Type text into an element. + pub async fn type_text(&self, selector: &str, text: &str) -> Result<(), CdpError> { + let element = self.page.find_element(selector).await?; + element.click().await?; + element.type_str(text).await?; + Ok(()) + } + + /// Wait for a selector to appear. + pub async fn wait_for(&self, selector: &str) -> Result<(), CdpError> { + self.page.find_element(selector).await?; + Ok(()) + } + + /// Wait for a selector with timeout. + pub async fn wait_for_timeout(&self, selector: &str, timeout_ms: u64) -> Result<(), CdpError> { + let timeout = std::time::Duration::from_millis(timeout_ms); + tokio::time::timeout(timeout, self.page.find_element(selector)) + .await + .map_err(|_| CdpError::Timeout)? + .map(|_| ()) + } + + /// Wait for navigation to complete. + pub async fn wait_for_navigation(&self) -> Result<(), CdpError> { + // Wait for load event + self.page.evaluate("new Promise(r => { if (document.readyState === 'complete') r(); else window.addEventListener('load', r); })").await?; + Ok(()) + } + + /// Wait for DOM to stabilize (no mutations for a period). + pub async fn wait_for_dom( + &self, + selector: Option<&str>, + timeout_ms: u32, + ) -> Result<(), CdpError> { + let sel = selector.unwrap_or("body"); + let script = format!( + r#" + new Promise((resolve, reject) => {{ + const timeout = {}; + const target = document.querySelector('{}'); + if (!target) {{ resolve(); return; }} + + let timer; + const observer = new MutationObserver(() => {{ + clearTimeout(timer); + timer = setTimeout(() => {{ + observer.disconnect(); + resolve(); + }}, 100); + }}); + + observer.observe(target, {{ childList: true, subtree: true, attributes: true }}); + + timer = setTimeout(() => {{ + observer.disconnect(); + resolve(); + }}, 100); + + setTimeout(() => {{ + observer.disconnect(); + resolve(); + }}, timeout); + }}) + "#, + timeout_ms, sel + ); + + self.page.evaluate(script).await?; + Ok(()) + } + + /// Wait for element then click it. + pub async fn wait_and_click(&self, selector: &str) -> Result<(), CdpError> { + let element = self.page.find_element(selector).await?; + element.click().await?; + Ok(()) + } + + /// Evaluate JavaScript and return the result. + pub async fn evaluate( + &self, + script: &str, + ) -> Result { + self.page + .evaluate(script) + .await? + .into_value() + .map_err(|e| CdpError::ChromeMessage(format!("JSON conversion error: {}", e))) + } + + /// Execute JavaScript without returning a value. + pub async fn execute(&self, script: &str) -> Result<(), CdpError> { + self.page.evaluate(script).await?; + Ok(()) + } + + /// Scroll horizontally by pixels. + pub async fn scroll_x(&self, pixels: i32) -> Result<(), CdpError> { + let script = format!("window.scrollBy({}, 0)", pixels); + self.page.evaluate(script).await?; + Ok(()) + } + + /// Scroll vertically by pixels. + pub async fn scroll_y(&self, pixels: i32) -> Result<(), CdpError> { + let script = format!("window.scrollBy(0, {})", pixels); + self.page.evaluate(script).await?; + Ok(()) + } + + /// Infinite scroll - scroll to the bottom of the page repeatedly. + /// Returns when no new content is loaded after scrolling. + pub async fn infinite_scroll(&self, max_scrolls: u32) -> Result { + let script = r#" + (async function() { + const maxScrolls = arguments[0]; + let lastHeight = document.body.scrollHeight; + let scrollCount = 0; + + while (scrollCount < maxScrolls) { + window.scrollTo(0, document.body.scrollHeight); + await new Promise(r => setTimeout(r, 1000)); + const newHeight = document.body.scrollHeight; + if (newHeight === lastHeight) break; + lastHeight = newHeight; + scrollCount++; + } + return scrollCount; + }) + "#; + + let count: usize = self + .page + .evaluate(format!("({script})({max_scrolls})")) + .await? + .into_value() + .unwrap_or(0); + + Ok(count) + } + + /// Fill an input element with a value (clears existing content first). + pub async fn fill(&self, selector: &str, value: &str) -> Result<(), CdpError> { + let element = self.page.find_element(selector).await?; + + // Clear existing value via triple-click + delete + element.click().await?; + element.click().await?; + element.click().await?; + + // Clear with keyboard + use chromiumoxide::cdp::browser_protocol::input::{ + DispatchKeyEventParams, DispatchKeyEventType, + }; + self.page + .execute( + DispatchKeyEventParams::builder() + .r#type(DispatchKeyEventType::KeyDown) + .key("a") + .modifiers(2) // Ctrl/Cmd + .build() + .map_err(|e| CdpError::ChromeMessage(format!("key event build: {e}")))?, + ) + .await?; + self.page + .execute( + DispatchKeyEventParams::builder() + .r#type(DispatchKeyEventType::KeyUp) + .key("a") + .build() + .map_err(|e| CdpError::ChromeMessage(format!("key event build: {e}")))?, + ) + .await?; + + // Type new value + element.type_str(value).await?; + Ok(()) + } + + /// Find all elements matching a selector. + pub async fn find_elements( + &self, + selector: &str, + ) -> Result, CdpError> { + self.page.find_elements(selector).await + } + + /// Get element bounding box via JavaScript. + pub async fn get_element_bounds( + &self, + selector: &str, + ) -> Result, CdpError> { + let escaped_selector = serde_json::to_string(selector).unwrap_or_else(|_| { + format!( + "\"{}\"", + selector.replace('\\', "\\\\").replace('"', "\\\"") + ) + }); + let script = format!( + r#" + (function() {{ + const el = document.querySelector({}); + if (!el) return null; + const rect = el.getBoundingClientRect(); + return [rect.x, rect.y, rect.width, rect.height]; + }})() + "#, + escaped_selector + ); + + let result: Option> = self + .page + .evaluate(script) + .await? + .into_value() + .map_err(|e| CdpError::ChromeMessage(format!("Failed to get bounds: {}", e)))?; + + Ok(result.and_then(|v| { + if v.len() >= 4 { + Some((v[0], v[1], v[2], v[3])) + } else { + None + } + })) + } + + /// Close the current page. + /// Note: This clones the page internally since close() takes ownership. + pub async fn close(&self) -> Result<(), CdpError> { + // Chrome pages need to be explicitly closed via the browser + // Since Page::close takes ownership, we use evaluate to close + self.page.evaluate("window.close()").await?; + Ok(()) + } + + /// Set the current page (switch to a different page). + pub fn set_page(&mut self, page: Arc) { + self.page = page; + } + + /// Create a new context with a different page (immutable version). + pub fn with_page(&self, page: Arc) -> Self { + Self { + browser: self.browser.clone(), + page, + } + } +} + +impl std::fmt::Debug for BrowserContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BrowserContext") + .field("browser", &"Browser { ... }") + .field("page", &"Page { ... }") + .finish() + } +} diff --git a/spider_agent/src/config.rs b/spider_agent/src/config.rs new file mode 100644 index 000000000..64fa07b92 --- /dev/null +++ b/spider_agent/src/config.rs @@ -0,0 +1,949 @@ +//! Configuration types for spider_agent. + +use dashmap::DashMap; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::Duration; + +/// Usage limits for controlling agent resource consumption. +#[derive(Debug, Clone, Default)] +pub struct UsageLimits { + /// Maximum total tokens (prompt + completion). + pub max_total_tokens: Option, + /// Maximum prompt tokens. + pub max_prompt_tokens: Option, + /// Maximum completion tokens. + pub max_completion_tokens: Option, + /// Maximum LLM API calls. + pub max_llm_calls: Option, + /// Maximum search API calls. + pub max_search_calls: Option, + /// Maximum HTTP fetch calls. + pub max_fetch_calls: Option, + /// Maximum web browser calls (Chrome/WebDriver combined). + pub max_webbrowser_calls: Option, + /// Maximum custom tool calls. + pub max_custom_tool_calls: Option, + /// Maximum generic tool calls. + pub max_tool_calls: Option, +} + +impl UsageLimits { + /// Create new usage limits with no restrictions. + pub fn new() -> Self { + Self::default() + } + + /// Set maximum total tokens. + pub fn with_max_total_tokens(mut self, limit: u64) -> Self { + self.max_total_tokens = Some(limit); + self + } + + /// Set maximum prompt tokens. + pub fn with_max_prompt_tokens(mut self, limit: u64) -> Self { + self.max_prompt_tokens = Some(limit); + self + } + + /// Set maximum completion tokens. + pub fn with_max_completion_tokens(mut self, limit: u64) -> Self { + self.max_completion_tokens = Some(limit); + self + } + + /// Set maximum LLM calls. + pub fn with_max_llm_calls(mut self, limit: u64) -> Self { + self.max_llm_calls = Some(limit); + self + } + + /// Set maximum search calls. + pub fn with_max_search_calls(mut self, limit: u64) -> Self { + self.max_search_calls = Some(limit); + self + } + + /// Set maximum fetch calls. + pub fn with_max_fetch_calls(mut self, limit: u64) -> Self { + self.max_fetch_calls = Some(limit); + self + } + + /// Set maximum web browser calls. + pub fn with_max_webbrowser_calls(mut self, limit: u64) -> Self { + self.max_webbrowser_calls = Some(limit); + self + } + + /// Set maximum custom tool calls. + pub fn with_max_custom_tool_calls(mut self, limit: u64) -> Self { + self.max_custom_tool_calls = Some(limit); + self + } + + /// Set maximum tool calls. + pub fn with_max_tool_calls(mut self, limit: u64) -> Self { + self.max_tool_calls = Some(limit); + self + } +} + +/// Type of limit that was exceeded. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum LimitType { + /// Total tokens limit exceeded. + TotalTokens { + /// Tokens used so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Prompt tokens limit exceeded. + PromptTokens { + /// Tokens used so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Completion tokens limit exceeded. + CompletionTokens { + /// Tokens used so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// LLM calls limit exceeded. + LlmCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Search calls limit exceeded. + SearchCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Fetch calls limit exceeded. + FetchCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Web browser calls limit exceeded. + WebbrowserCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Custom tool calls limit exceeded. + CustomToolCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, + /// Tool calls limit exceeded. + ToolCalls { + /// Calls made so far. + used: u64, + /// The limit that was set. + limit: u64, + }, +} + +impl std::fmt::Display for LimitType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::TotalTokens { used, limit } => { + write!(f, "total tokens ({} used, {} limit)", used, limit) + } + Self::PromptTokens { used, limit } => { + write!(f, "prompt tokens ({} used, {} limit)", used, limit) + } + Self::CompletionTokens { used, limit } => { + write!(f, "completion tokens ({} used, {} limit)", used, limit) + } + Self::LlmCalls { used, limit } => { + write!(f, "LLM calls ({} used, {} limit)", used, limit) + } + Self::SearchCalls { used, limit } => { + write!(f, "search calls ({} used, {} limit)", used, limit) + } + Self::FetchCalls { used, limit } => { + write!(f, "fetch calls ({} used, {} limit)", used, limit) + } + Self::WebbrowserCalls { used, limit } => { + write!(f, "web browser calls ({} used, {} limit)", used, limit) + } + Self::CustomToolCalls { used, limit } => { + write!(f, "custom tool calls ({} used, {} limit)", used, limit) + } + Self::ToolCalls { used, limit } => { + write!(f, "tool calls ({} used, {} limit)", used, limit) + } + } + } +} + +/// Agent configuration. +#[derive(Debug, Clone)] +pub struct AgentConfig { + /// System prompt for LLM. + pub system_prompt: Option, + + /// Max concurrent LLM calls. + pub max_concurrent_llm_calls: usize, + + /// LLM temperature (0.0 - 1.0). + pub temperature: f32, + + /// Max tokens for LLM response. + pub max_tokens: u16, + + /// Request timeout. + pub timeout: Duration, + + /// Retry configuration. + pub retry: RetryConfig, + + /// Max HTML bytes to send to LLM. + pub html_max_bytes: usize, + + /// HTML cleaning mode. + pub html_cleaning: HtmlCleaningMode, + + /// Whether to request JSON output from LLM. + pub json_mode: bool, + + /// Usage limits for resource control. + pub limits: UsageLimits, +} + +impl Default for AgentConfig { + fn default() -> Self { + Self { + system_prompt: None, + max_concurrent_llm_calls: 5, + temperature: 0.1, + max_tokens: 4096, + timeout: Duration::from_secs(60), + retry: RetryConfig::default(), + html_max_bytes: 24_000, + html_cleaning: HtmlCleaningMode::Default, + json_mode: true, + limits: UsageLimits::default(), + } + } +} + +impl AgentConfig { + /// Create a new config with defaults. + pub fn new() -> Self { + Self::default() + } + + /// Set system prompt. + pub fn with_system_prompt(mut self, prompt: impl Into) -> Self { + self.system_prompt = Some(prompt.into()); + self + } + + /// Set max concurrent LLM calls. + pub fn with_max_concurrent_llm_calls(mut self, n: usize) -> Self { + self.max_concurrent_llm_calls = n; + self + } + + /// Set LLM temperature. + pub fn with_temperature(mut self, temp: f32) -> Self { + self.temperature = temp.clamp(0.0, 2.0); + self + } + + /// Set max tokens. + pub fn with_max_tokens(mut self, tokens: u16) -> Self { + self.max_tokens = tokens; + self + } + + /// Set request timeout. + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Set retry config. + pub fn with_retry(mut self, retry: RetryConfig) -> Self { + self.retry = retry; + self + } + + /// Set HTML max bytes. + pub fn with_html_max_bytes(mut self, bytes: usize) -> Self { + self.html_max_bytes = bytes; + self + } + + /// Set HTML cleaning mode. + pub fn with_html_cleaning(mut self, mode: HtmlCleaningMode) -> Self { + self.html_cleaning = mode; + self + } + + /// Enable or disable JSON mode. + pub fn with_json_mode(mut self, enabled: bool) -> Self { + self.json_mode = enabled; + self + } + + /// Set usage limits. + pub fn with_limits(mut self, limits: UsageLimits) -> Self { + self.limits = limits; + self + } +} + +/// Retry configuration. +#[derive(Debug, Clone)] +pub struct RetryConfig { + /// Max retry attempts. + pub max_attempts: usize, + /// Backoff delay between attempts. + pub backoff: Duration, + /// Retry on parse errors. + pub retry_on_parse_error: bool, +} + +impl Default for RetryConfig { + fn default() -> Self { + Self { + max_attempts: 3, + backoff: Duration::from_millis(500), + retry_on_parse_error: true, + } + } +} + +impl RetryConfig { + /// Create a new retry config. + pub fn new() -> Self { + Self::default() + } + + /// Set max attempts. + pub fn with_max_attempts(mut self, n: usize) -> Self { + self.max_attempts = n; + self + } + + /// Set backoff delay. + pub fn with_backoff(mut self, backoff: Duration) -> Self { + self.backoff = backoff; + self + } +} + +/// HTML cleaning mode. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HtmlCleaningMode { + /// Standard cleaning - removes scripts, styles, comments. + #[default] + Default, + /// Aggressive cleaning - also removes SVGs, images, etc. + Aggressive, + /// Minimal cleaning - only removes scripts. + Minimal, + /// No cleaning - raw HTML. + Raw, +} + +/// Search options for web search. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SearchOptions { + /// Maximum number of results to return. + pub limit: Option, + /// Country/region code (e.g., "us", "uk"). + pub country: Option, + /// Language code (e.g., "en", "es"). + pub language: Option, + /// Filter to specific domains. + pub site_filter: Option>, + /// Exclude specific domains. + pub exclude_domains: Option>, + /// Time range filter. + pub time_range: Option, +} + +impl SearchOptions { + /// Create new search options with defaults. + pub fn new() -> Self { + Self::default() + } + + /// Set maximum number of results. + pub fn with_limit(mut self, limit: usize) -> Self { + self.limit = Some(limit); + self + } + + /// Set country/region code. + pub fn with_country(mut self, country: impl Into) -> Self { + self.country = Some(country.into()); + self + } + + /// Set language code. + pub fn with_language(mut self, language: impl Into) -> Self { + self.language = Some(language.into()); + self + } + + /// Filter results to specific domains. + pub fn with_site_filter(mut self, domains: Vec) -> Self { + self.site_filter = Some(domains); + self + } + + /// Exclude specific domains from results. + pub fn with_exclude_domains(mut self, domains: Vec) -> Self { + self.exclude_domains = Some(domains); + self + } + + /// Set time range filter. + pub fn with_time_range(mut self, range: TimeRange) -> Self { + self.time_range = Some(range); + self + } +} + +/// Time range for filtering search results. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TimeRange { + /// Results from the past day. + Day, + /// Results from the past week. + Week, + /// Results from the past month. + Month, + /// Results from the past year. + Year, + /// Custom date range. + Custom { + /// Start date (format depends on provider). + start: String, + /// End date (format depends on provider). + end: String, + }, +} + +/// Research options for research tasks. +#[derive(Debug, Clone, Default)] +pub struct ResearchOptions { + /// Maximum pages to visit. + pub max_pages: usize, + /// Search options for the query. + pub search_options: Option, + /// Custom extraction prompt. + pub extraction_prompt: Option, + /// Whether to synthesize findings into a summary. + pub synthesize: bool, +} + +impl ResearchOptions { + /// Create new research options with defaults. + pub fn new() -> Self { + Self { + max_pages: 5, + search_options: None, + extraction_prompt: None, + synthesize: true, + } + } + + /// Set max pages to visit. + pub fn with_max_pages(mut self, n: usize) -> Self { + self.max_pages = n; + self + } + + /// Set search options. + pub fn with_search_options(mut self, options: SearchOptions) -> Self { + self.search_options = Some(options); + self + } + + /// Set extraction prompt. + pub fn with_extraction_prompt(mut self, prompt: impl Into) -> Self { + self.extraction_prompt = Some(prompt.into()); + self + } + + /// Enable or disable synthesis. + pub fn with_synthesize(mut self, enabled: bool) -> Self { + self.synthesize = enabled; + self + } +} + +/// Usage statistics for tracking agent operations. +/// +/// Uses atomic counters for lock-free concurrent updates. +#[derive(Debug)] +pub struct UsageStats { + /// Total LLM prompt tokens used. + pub prompt_tokens: AtomicU64, + /// Total LLM completion tokens used. + pub completion_tokens: AtomicU64, + /// Total LLM calls made. + pub llm_calls: AtomicU64, + /// Total search calls made. + pub search_calls: AtomicU64, + /// Total HTTP fetch calls made. + pub fetch_calls: AtomicU64, + /// Total web browser calls made (Chrome/WebDriver combined). + pub webbrowser_calls: AtomicU64, + /// Custom tool calls tracked by tool name (lock-free via DashMap). + pub custom_tool_calls: DashMap, + /// Total tool calls made. + pub tool_calls: AtomicU64, +} + +impl Default for UsageStats { + fn default() -> Self { + Self { + prompt_tokens: AtomicU64::new(0), + completion_tokens: AtomicU64::new(0), + llm_calls: AtomicU64::new(0), + search_calls: AtomicU64::new(0), + fetch_calls: AtomicU64::new(0), + webbrowser_calls: AtomicU64::new(0), + custom_tool_calls: DashMap::new(), + tool_calls: AtomicU64::new(0), + } + } +} + +impl UsageStats { + /// Create new usage stats. + pub fn new() -> Self { + Self::default() + } + + /// Add tokens from an LLM response. + pub fn add_tokens(&self, prompt: u64, completion: u64) { + self.prompt_tokens.fetch_add(prompt, Ordering::Relaxed); + self.completion_tokens + .fetch_add(completion, Ordering::Relaxed); + } + + /// Increment LLM call count. + pub fn increment_llm_calls(&self) { + self.llm_calls.fetch_add(1, Ordering::Relaxed); + } + + /// Increment search call count. + pub fn increment_search_calls(&self) { + self.search_calls.fetch_add(1, Ordering::Relaxed); + } + + /// Increment fetch call count. + pub fn increment_fetch_calls(&self) { + self.fetch_calls.fetch_add(1, Ordering::Relaxed); + } + + /// Increment web browser call count (Chrome/WebDriver). + pub fn increment_webbrowser_calls(&self) { + self.webbrowser_calls.fetch_add(1, Ordering::Relaxed); + } + + /// Increment custom tool call count for a specific tool. + pub fn increment_custom_tool_calls(&self, tool_name: &str) { + self.custom_tool_calls + .entry(tool_name.to_string()) + .or_insert_with(|| AtomicU64::new(0)) + .fetch_add(1, Ordering::Relaxed); + } + + /// Get custom tool call count for a specific tool. + pub fn get_custom_tool_calls(&self, tool_name: &str) -> u64 { + self.custom_tool_calls + .get(tool_name) + .map(|v| v.load(Ordering::Relaxed)) + .unwrap_or(0) + } + + /// Get total custom tool calls across all tools. + pub fn total_custom_tool_calls(&self) -> u64 { + self.custom_tool_calls + .iter() + .map(|entry| entry.value().load(Ordering::Relaxed)) + .sum() + } + + /// Increment tool call count. + pub fn increment_tool_calls(&self) { + self.tool_calls.fetch_add(1, Ordering::Relaxed); + } + + /// Get total tokens used. + pub fn total_tokens(&self) -> u64 { + self.prompt_tokens.load(Ordering::Relaxed) + self.completion_tokens.load(Ordering::Relaxed) + } + + /// Get a snapshot of all stats. + pub fn snapshot(&self) -> UsageSnapshot { + let custom_tool_calls: HashMap = self + .custom_tool_calls + .iter() + .map(|entry| (entry.key().clone(), entry.value().load(Ordering::Relaxed))) + .collect(); + + UsageSnapshot { + prompt_tokens: self.prompt_tokens.load(Ordering::Relaxed), + completion_tokens: self.completion_tokens.load(Ordering::Relaxed), + llm_calls: self.llm_calls.load(Ordering::Relaxed), + search_calls: self.search_calls.load(Ordering::Relaxed), + fetch_calls: self.fetch_calls.load(Ordering::Relaxed), + webbrowser_calls: self.webbrowser_calls.load(Ordering::Relaxed), + custom_tool_calls, + tool_calls: self.tool_calls.load(Ordering::Relaxed), + } + } + + /// Reset all counters. + pub fn reset(&self) { + self.prompt_tokens.store(0, Ordering::Relaxed); + self.completion_tokens.store(0, Ordering::Relaxed); + self.llm_calls.store(0, Ordering::Relaxed); + self.search_calls.store(0, Ordering::Relaxed); + self.fetch_calls.store(0, Ordering::Relaxed); + self.webbrowser_calls.store(0, Ordering::Relaxed); + self.custom_tool_calls.clear(); + self.tool_calls.store(0, Ordering::Relaxed); + } + + // ==================== Limit Checking Methods ==================== + + /// Check if LLM call limit would be exceeded. + pub fn check_llm_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_llm_calls { + let used = self.llm_calls.load(Ordering::Relaxed); + if used >= limit { + return Some(LimitType::LlmCalls { used, limit }); + } + } + None + } + + /// Check if search call limit would be exceeded. + pub fn check_search_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_search_calls { + let used = self.search_calls.load(Ordering::Relaxed); + if used >= limit { + return Some(LimitType::SearchCalls { used, limit }); + } + } + None + } + + /// Check if fetch call limit would be exceeded. + pub fn check_fetch_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_fetch_calls { + let used = self.fetch_calls.load(Ordering::Relaxed); + if used >= limit { + return Some(LimitType::FetchCalls { used, limit }); + } + } + None + } + + /// Check if web browser call limit would be exceeded. + pub fn check_webbrowser_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_webbrowser_calls { + let used = self.webbrowser_calls.load(Ordering::Relaxed); + if used >= limit { + return Some(LimitType::WebbrowserCalls { used, limit }); + } + } + None + } + + /// Check if custom tool call limit would be exceeded (total across all tools). + pub fn check_custom_tool_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_custom_tool_calls { + let used = self.total_custom_tool_calls(); + if used >= limit { + return Some(LimitType::CustomToolCalls { used, limit }); + } + } + None + } + + /// Check if tool call limit would be exceeded. + pub fn check_tool_limit(&self, limits: &UsageLimits) -> Option { + if let Some(limit) = limits.max_tool_calls { + let used = self.tool_calls.load(Ordering::Relaxed); + if used >= limit { + return Some(LimitType::ToolCalls { used, limit }); + } + } + None + } + + /// Check if token limits would be exceeded. + pub fn check_token_limits(&self, limits: &UsageLimits) -> Option { + let prompt = self.prompt_tokens.load(Ordering::Relaxed); + let completion = self.completion_tokens.load(Ordering::Relaxed); + let total = prompt + completion; + + if let Some(limit) = limits.max_total_tokens { + if total >= limit { + return Some(LimitType::TotalTokens { used: total, limit }); + } + } + + if let Some(limit) = limits.max_prompt_tokens { + if prompt >= limit { + return Some(LimitType::PromptTokens { + used: prompt, + limit, + }); + } + } + + if let Some(limit) = limits.max_completion_tokens { + if completion >= limit { + return Some(LimitType::CompletionTokens { + used: completion, + limit, + }); + } + } + + None + } +} + +/// Snapshot of usage statistics. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct UsageSnapshot { + /// Total LLM prompt tokens. + pub prompt_tokens: u64, + /// Total LLM completion tokens. + pub completion_tokens: u64, + /// Total LLM calls. + pub llm_calls: u64, + /// Total search calls. + pub search_calls: u64, + /// Total HTTP fetch calls. + pub fetch_calls: u64, + /// Total web browser calls (Chrome/WebDriver combined). + pub webbrowser_calls: u64, + /// Custom tool calls by tool name. + pub custom_tool_calls: HashMap, + /// Total tool calls. + pub tool_calls: u64, +} + +impl UsageSnapshot { + /// Get total tokens. + pub fn total_tokens(&self) -> u64 { + self.prompt_tokens + self.completion_tokens + } + + /// Get total custom tool calls across all tools. + pub fn total_custom_tool_calls(&self) -> u64 { + self.custom_tool_calls.values().sum() + } + + /// Get call count for a specific custom tool. + pub fn get_custom_tool_calls(&self, tool_name: &str) -> u64 { + self.custom_tool_calls.get(tool_name).copied().unwrap_or(0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_usage_limits_builder() { + let limits = UsageLimits::new() + .with_max_total_tokens(10000) + .with_max_llm_calls(100) + .with_max_search_calls(50) + .with_max_fetch_calls(200) + .with_max_webbrowser_calls(30) + .with_max_custom_tool_calls(25) + .with_max_tool_calls(500); + + assert_eq!(limits.max_total_tokens, Some(10000)); + assert_eq!(limits.max_llm_calls, Some(100)); + assert_eq!(limits.max_search_calls, Some(50)); + assert_eq!(limits.max_fetch_calls, Some(200)); + assert_eq!(limits.max_webbrowser_calls, Some(30)); + assert_eq!(limits.max_custom_tool_calls, Some(25)); + assert_eq!(limits.max_tool_calls, Some(500)); + } + + #[test] + fn test_usage_stats_tracking() { + let stats = UsageStats::new(); + + // Track various calls + stats.increment_llm_calls(); + stats.increment_llm_calls(); + stats.increment_search_calls(); + stats.increment_fetch_calls(); + stats.increment_fetch_calls(); + stats.increment_fetch_calls(); + stats.increment_webbrowser_calls(); + stats.increment_custom_tool_calls("my_api"); + stats.increment_custom_tool_calls("my_api"); + stats.increment_custom_tool_calls("other_api"); + stats.add_tokens(100, 50); + + let snapshot = stats.snapshot(); + assert_eq!(snapshot.llm_calls, 2); + assert_eq!(snapshot.search_calls, 1); + assert_eq!(snapshot.fetch_calls, 3); + assert_eq!(snapshot.webbrowser_calls, 1); + assert_eq!(snapshot.prompt_tokens, 100); + assert_eq!(snapshot.completion_tokens, 50); + assert_eq!(snapshot.total_tokens(), 150); + + // Check custom tool tracking + assert_eq!(snapshot.get_custom_tool_calls("my_api"), 2); + assert_eq!(snapshot.get_custom_tool_calls("other_api"), 1); + assert_eq!(snapshot.get_custom_tool_calls("unknown"), 0); + assert_eq!(snapshot.total_custom_tool_calls(), 3); + } + + #[test] + fn test_usage_stats_reset() { + let stats = UsageStats::new(); + stats.increment_llm_calls(); + stats.increment_search_calls(); + stats.increment_custom_tool_calls("my_api"); + stats.add_tokens(100, 50); + + stats.reset(); + + let snapshot = stats.snapshot(); + assert_eq!(snapshot.llm_calls, 0); + assert_eq!(snapshot.search_calls, 0); + assert_eq!(snapshot.prompt_tokens, 0); + assert_eq!(snapshot.total_custom_tool_calls(), 0); + } + + #[test] + fn test_limit_checking_llm() { + let stats = UsageStats::new(); + let limits = UsageLimits::new().with_max_llm_calls(3); + + // Under limit + stats.increment_llm_calls(); + stats.increment_llm_calls(); + assert!(stats.check_llm_limit(&limits).is_none()); + + // At limit + stats.increment_llm_calls(); + let exceeded = stats.check_llm_limit(&limits); + assert!(exceeded.is_some()); + match exceeded.unwrap() { + LimitType::LlmCalls { used, limit } => { + assert_eq!(used, 3); + assert_eq!(limit, 3); + } + _ => panic!("Expected LlmCalls limit type"), + } + } + + #[test] + fn test_limit_checking_tokens() { + let stats = UsageStats::new(); + let limits = UsageLimits::new() + .with_max_total_tokens(100) + .with_max_prompt_tokens(60); + + // Under limit + stats.add_tokens(30, 20); + assert!(stats.check_token_limits(&limits).is_none()); + + // Prompt limit exceeded + stats.add_tokens(40, 0); + let exceeded = stats.check_token_limits(&limits); + assert!(exceeded.is_some()); + match exceeded.unwrap() { + LimitType::PromptTokens { used, limit } => { + assert_eq!(used, 70); + assert_eq!(limit, 60); + } + _ => panic!("Expected PromptTokens limit type"), + } + } + + #[test] + fn test_limit_checking_custom_tools() { + let stats = UsageStats::new(); + let limits = UsageLimits::new().with_max_custom_tool_calls(5); + + stats.increment_custom_tool_calls("api_a"); + stats.increment_custom_tool_calls("api_b"); + stats.increment_custom_tool_calls("api_a"); + stats.increment_custom_tool_calls("api_c"); + assert!(stats.check_custom_tool_limit(&limits).is_none()); + + stats.increment_custom_tool_calls("api_a"); + let exceeded = stats.check_custom_tool_limit(&limits); + assert!(exceeded.is_some()); + match exceeded.unwrap() { + LimitType::CustomToolCalls { used, limit } => { + assert_eq!(used, 5); + assert_eq!(limit, 5); + } + _ => panic!("Expected CustomToolCalls limit type"), + } + } + + #[test] + fn test_agent_config_with_limits() { + let limits = UsageLimits::new() + .with_max_llm_calls(100) + .with_max_search_calls(50); + + let config = AgentConfig::new().with_limits(limits); + + assert_eq!(config.limits.max_llm_calls, Some(100)); + assert_eq!(config.limits.max_search_calls, Some(50)); + } + + #[test] + fn test_limit_type_display() { + let limit = LimitType::LlmCalls { used: 10, limit: 5 }; + assert_eq!(format!("{}", limit), "LLM calls (10 used, 5 limit)"); + + let limit = LimitType::CustomToolCalls { + used: 25, + limit: 20, + }; + assert_eq!( + format!("{}", limit), + "custom tool calls (25 used, 20 limit)" + ); + + let limit = LimitType::TotalTokens { + used: 1000, + limit: 500, + }; + assert_eq!(format!("{}", limit), "total tokens (1000 used, 500 limit)"); + } +} diff --git a/spider_agent/src/error.rs b/spider_agent/src/error.rs new file mode 100644 index 000000000..a33a834b2 --- /dev/null +++ b/spider_agent/src/error.rs @@ -0,0 +1,235 @@ +//! Error types for spider_agent. + +use crate::config::LimitType; +use std::fmt; + +/// Agent error types. +#[derive(Debug)] +pub enum AgentError { + /// HTTP request failed. + Http(reqwest::Error), + /// JSON serialization/deserialization error. + Json(serde_json::Error), + /// Missing required field in response. + MissingField(&'static str), + /// Invalid field type in response. + InvalidField(&'static str), + /// Remote API error. + Remote(String), + /// Feature not enabled or configured. + NotConfigured(&'static str), + /// Search error. + Search(SearchError), + /// LLM provider error. + Llm(String), + /// Browser automation error. + #[cfg(feature = "chrome")] + Browser(String), + /// WebDriver automation error. + #[cfg(feature = "webdriver")] + WebDriver(String), + /// IO error (file operations). + #[cfg(feature = "fs")] + Io(std::io::Error), + /// Tool execution error. + Tool(String), + /// Rate limit exceeded. + RateLimited, + /// Timeout. + Timeout, + /// Usage limit exceeded. + LimitExceeded(LimitType), +} + +impl fmt::Display for AgentError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Http(e) => write!(f, "HTTP error: {}", e), + Self::Json(e) => write!(f, "JSON error: {}", e), + Self::MissingField(field) => write!(f, "Missing field: {}", field), + Self::InvalidField(field) => write!(f, "Invalid field: {}", field), + Self::Remote(msg) => write!(f, "Remote error: {}", msg), + Self::NotConfigured(what) => write!(f, "Not configured: {}", what), + Self::Search(e) => write!(f, "Search error: {}", e), + Self::Llm(msg) => write!(f, "LLM error: {}", msg), + #[cfg(feature = "chrome")] + Self::Browser(msg) => write!(f, "Browser error: {}", msg), + #[cfg(feature = "webdriver")] + Self::WebDriver(msg) => write!(f, "WebDriver error: {}", msg), + #[cfg(feature = "fs")] + Self::Io(e) => write!(f, "IO error: {}", e), + Self::Tool(msg) => write!(f, "Tool error: {}", msg), + Self::RateLimited => write!(f, "Rate limit exceeded"), + Self::Timeout => write!(f, "Request timed out"), + Self::LimitExceeded(limit) => write!(f, "Usage limit exceeded: {}", limit), + } + } +} + +impl std::error::Error for AgentError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Http(e) => Some(e), + Self::Json(e) => Some(e), + Self::Search(e) => Some(e), + #[cfg(feature = "fs")] + Self::Io(e) => Some(e), + _ => None, + } + } +} + +impl From for AgentError { + fn from(e: reqwest::Error) -> Self { + if e.is_timeout() { + Self::Timeout + } else { + Self::Http(e) + } + } +} + +impl From for AgentError { + fn from(e: serde_json::Error) -> Self { + Self::Json(e) + } +} + +impl From for AgentError { + fn from(e: SearchError) -> Self { + Self::Search(e) + } +} + +/// Search-specific error types. +#[derive(Debug)] +pub enum SearchError { + /// API request failed. + RequestFailed(String), + /// Invalid API key or authentication. + AuthenticationFailed, + /// Rate limit exceeded. + RateLimited, + /// Invalid query or parameters. + InvalidQuery(String), + /// Provider-specific error. + ProviderError(String), + /// No search provider configured. + NoProvider, +} + +impl fmt::Display for SearchError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::RequestFailed(msg) => write!(f, "Search request failed: {}", msg), + Self::AuthenticationFailed => write!(f, "Search authentication failed"), + Self::RateLimited => write!(f, "Search rate limit exceeded"), + Self::InvalidQuery(msg) => write!(f, "Invalid search query: {}", msg), + Self::ProviderError(msg) => write!(f, "Search provider error: {}", msg), + Self::NoProvider => write!(f, "No search provider configured"), + } + } +} + +impl std::error::Error for SearchError {} + +/// Result type for agent operations. +pub type AgentResult = Result; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_agent_error_display_variants() { + let err = AgentError::MissingField("content"); + assert_eq!(format!("{}", err), "Missing field: content"); + + let err = AgentError::InvalidField("type"); + assert_eq!(format!("{}", err), "Invalid field: type"); + + let err = AgentError::Remote("server down".into()); + assert_eq!(format!("{}", err), "Remote error: server down"); + + let err = AgentError::NotConfigured("api_key"); + assert_eq!(format!("{}", err), "Not configured: api_key"); + + let err = AgentError::Llm("model not found".into()); + assert_eq!(format!("{}", err), "LLM error: model not found"); + + let err = AgentError::Tool("execution failed".into()); + assert_eq!(format!("{}", err), "Tool error: execution failed"); + + let err = AgentError::RateLimited; + assert_eq!(format!("{}", err), "Rate limit exceeded"); + + let err = AgentError::Timeout; + assert_eq!(format!("{}", err), "Request timed out"); + + let err = AgentError::LimitExceeded(LimitType::TotalTokens { + used: 100, + limit: 50, + }); + assert!(format!("{}", err).contains("Usage limit exceeded")); + } + + #[test] + fn test_search_error_display_variants() { + assert_eq!( + format!("{}", SearchError::RequestFailed("timeout".into())), + "Search request failed: timeout" + ); + assert_eq!( + format!("{}", SearchError::AuthenticationFailed), + "Search authentication failed" + ); + assert_eq!( + format!("{}", SearchError::RateLimited), + "Search rate limit exceeded" + ); + assert_eq!( + format!("{}", SearchError::InvalidQuery("empty".into())), + "Invalid search query: empty" + ); + assert_eq!( + format!("{}", SearchError::ProviderError("api error".into())), + "Search provider error: api error" + ); + assert_eq!( + format!("{}", SearchError::NoProvider), + "No search provider configured" + ); + } + + #[test] + fn test_from_serde_json_error() { + let json_err = serde_json::from_str::("invalid").unwrap_err(); + let agent_err: AgentError = json_err.into(); + assert!(format!("{}", agent_err).starts_with("JSON error:")); + } + + #[test] + fn test_from_search_error() { + let search_err = SearchError::NoProvider; + let agent_err: AgentError = search_err.into(); + assert!(format!("{}", agent_err).contains("Search error:")); + } + + #[test] + fn test_error_source_chain() { + use std::error::Error; + + let json_err = serde_json::from_str::("invalid").unwrap_err(); + let agent_err = AgentError::Json(json_err); + assert!(agent_err.source().is_some()); + + let search_err = AgentError::Search(SearchError::NoProvider); + assert!(search_err.source().is_some()); + + let remote_err = AgentError::Remote("test".into()); + assert!(remote_err.source().is_none()); + + let timeout_err = AgentError::Timeout; + assert!(timeout_err.source().is_none()); + } +} diff --git a/spider_agent/src/lib.rs b/spider_agent/src/lib.rs new file mode 100644 index 000000000..5da956cfc --- /dev/null +++ b/spider_agent/src/lib.rs @@ -0,0 +1,262 @@ +//! # Spider Agent +//! +//! A concurrent-safe multimodal agent for web automation and research. +//! +//! ## Features +//! +//! - **Concurrent-safe**: Designed to be wrapped in `Arc` for multi-task access +//! - **Feature-gated**: Only include dependencies you need +//! - **Multiple LLM providers**: OpenAI, OpenAI-compatible APIs +//! - **Multiple search providers**: Serper, Brave, Bing, Tavily +//! - **Browser automation**: Chrome support via chromiumoxide +//! +//! ## Quick Start +//! +//! ```rust,ignore +//! use spider_agent::{Agent, AgentConfig}; +//! use std::sync::Arc; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let agent = Arc::new(Agent::builder() +//! .with_openai("sk-...", "gpt-4o-mini") +//! .with_search_serper("serper-key") +//! .build()?); +//! +//! // Search +//! let results = agent.search("rust web frameworks").await?; +//! println!("Found {} results", results.len()); +//! +//! // Extract from first result +//! let html = agent.fetch(&results.results[0].url).await?.html; +//! let data = agent.extract(&html, "Extract framework name and features").await?; +//! println!("{}", serde_json::to_string_pretty(&data)?); +//! +//! Ok(()) +//! } +//! ``` +//! +//! ## Concurrent Execution +//! +//! ```rust,ignore +//! use spider_agent::Agent; +//! use std::sync::Arc; +//! +//! let agent = Arc::new(Agent::builder() +//! .with_openai("sk-...", "gpt-4o") +//! .with_search_serper("serper-key") +//! .with_max_concurrent_llm_calls(10) +//! .build()?); +//! +//! // Execute multiple searches concurrently +//! let queries = vec!["rust async", "rust web frameworks", "rust databases"]; +//! let mut handles = Vec::new(); +//! +//! for query in queries { +//! let agent = agent.clone(); +//! let query = query.to_string(); +//! handles.push(tokio::spawn(async move { +//! agent.search(&query).await +//! })); +//! } +//! +//! // Collect results +//! for handle in handles { +//! let result = handle.await??; +//! println!("Found {} results", result.results.len()); +//! } +//! ``` +//! +//! ## Feature Flags +//! +//! - `openai` - OpenAI/OpenAI-compatible LLM provider +//! - `chrome` - Browser automation via chromiumoxide +//! - `search` - Base search functionality +//! - `search_serper` - Serper.dev search provider +//! - `search_brave` - Brave Search provider +//! - `search_bing` - Bing Search provider +//! - `search_tavily` - Tavily AI Search provider +//! - `full` - All features + +#![warn(missing_docs)] + +mod agent; +pub mod automation; +mod config; +mod error; +mod llm; +mod memory; +pub mod tools; + +#[cfg(feature = "search")] +pub mod search; + +#[cfg(feature = "chrome")] +pub mod browser; + +#[cfg(feature = "webdriver")] +pub mod webdriver; + +#[cfg(feature = "fs")] +pub mod temp; + +// Re-exports +pub use agent::{Agent, AgentBuilder, FetchResult, PageExtraction}; +pub use config::{ + AgentConfig, HtmlCleaningMode, LimitType, ResearchOptions, RetryConfig, SearchOptions, + TimeRange, UsageLimits, UsageSnapshot, UsageStats, +}; +pub use error::{AgentError, AgentResult, SearchError}; +pub use llm::{ + CompletionOptions, CompletionResponse, LLMProvider, Message, MessageContent, TokenUsage, +}; +pub use memory::AgentMemory; +pub use tools::{ + AuthConfig, CustomTool, CustomToolRegistry, CustomToolResult, HttpMethod, SpiderCloudToolConfig, +}; + +// Automation re-exports - core types +pub use automation::{ + ActResult, ActionRecord, ActionResult, ActionType, AutomationConfig, AutomationResult, + AutomationUsage, CaptureProfile, ChainBuilder, ChainCondition, ChainContext, ChainResult, + ChainStep, ChainStepResult, CleaningIntent, ClipViewport, ContentAnalysis, CostTier, + ExtractionSchema, FormField, FormInfo, HtmlCleaningProfile, InteractiveElement, + ModelCapabilities, ModelEndpoint, ModelInfoEntry, ModelPolicy, ModelPricing, ModelProfile, + ModelRanks, NavigationOption, PageObservation, PromptUrlGate, ReasoningEffort, + RecoveryStrategy, RetryPolicy, SelectorCache, SelectorCacheEntry, StructuredOutputConfig, + VisionRouteMode, MODEL_INFO, +}; + +// Automation re-exports - engine and configuration +pub use automation::{RemoteMultimodalConfig, RemoteMultimodalConfigs, RemoteMultimodalEngine}; + +// Automation re-exports - engine error types +pub use automation::{EngineError, EngineResult}; + +// Automation re-exports - helper functions +pub use automation::{ + best_effort_parse_json_object, extract_assistant_content, extract_last_code_block, + extract_last_json_array, extract_last_json_boundaries, extract_last_json_object, extract_usage, + fnv1a64, truncate_utf8_tail, +}; + +// Automation re-exports - HTML cleaning +pub use automation::{ + clean_html, clean_html_base, clean_html_full, clean_html_raw, clean_html_slim, + clean_html_with_profile, clean_html_with_profile_and_intent, smart_clean_html, +}; + +// Automation re-exports - map result types +pub use automation::{categories, DiscoveredUrl, MapResult}; + +// Automation re-exports - memory operations +pub use automation::{AutomationMemory, MemoryOperation}; + +// Automation re-exports - system prompts +pub use automation::{ + ACT_SYSTEM_PROMPT, CONFIGURATION_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, EXTRACT_SYSTEM_PROMPT, + MAP_SYSTEM_PROMPT, OBSERVE_SYSTEM_PROMPT, +}; + +// Automation re-exports - concurrent chain types +pub use automation::{ + ConcurrentChainConfig, ConcurrentChainResult, DependencyGraph, DependentStep, StepResult, +}; + +// Automation re-exports - confidence types +pub use automation::{ + Alternative, ConfidenceRetryStrategy, ConfidenceSummary, ConfidenceTracker, ConfidentStep, + Verification, VerificationType, +}; + +// Automation re-exports - tool calling types +pub use automation::{ + ActionToolSchemas, FunctionCall, FunctionDefinition, ToolCall, ToolCallingMode, ToolDefinition, +}; + +// Automation re-exports - HTML diff types +pub use automation::{ + ChangeType, DiffStats, ElementChange, HtmlDiffMode, HtmlDiffResult, PageStateDiff, +}; + +// Automation re-exports - planning types +pub use automation::{ + Checkpoint, CheckpointResult, CheckpointType, ExecutionPlan, PageState, PlanExecutionState, + PlannedStep, PlanningModeConfig, ReplanContext, +}; + +// Automation re-exports - self-healing types +pub use automation::{ + HealedSelectorCache, HealingDiagnosis, HealingRequest, HealingResult, HealingStats, + SelectorIssueType, SelfHealingConfig, +}; + +// Automation re-exports - synthesis types +pub use automation::{ + MultiPageContext, PageContext, PageContribution, SynthesisConfig, SynthesisResult, +}; + +// Automation re-exports - schema generation types +pub use automation::{ + build_schema_generation_prompt, generate_schema, infer_schema, infer_schema_from_examples, + refine_schema, GeneratedSchema, SchemaCache, SchemaGenerationRequest, +}; + +// Automation re-exports - self-healing helper functions +pub use automation::extract_html_context; + +// Automation re-exports - tool calling helper functions +pub use automation::{parse_tool_calls, tool_calls_to_steps}; + +// Automation re-exports - concurrent chain execution +pub use automation::execute_graph; + +// Performance re-exports +pub use automation::cache::{CacheStats, CacheValue, SmartCache}; +pub use automation::executor::{BatchExecutor, ChainExecutor, PrefetchManager}; +pub use automation::router::{ + auto_policy, estimate_tokens, ModelRequirements, ModelRouter, ModelSelector, RoutingDecision, + ScoredModel, SelectionStrategy, TaskAnalysis, TaskCategory, +}; + +#[cfg(feature = "search")] +pub use agent::ResearchResult; + +#[cfg(feature = "search")] +pub use search::{SearchProvider, SearchResult, SearchResults}; + +#[cfg(feature = "openai")] +pub use llm::OpenAIProvider; + +#[cfg(feature = "search_serper")] +pub use search::SerperProvider; + +#[cfg(feature = "search_brave")] +pub use search::BraveProvider; + +#[cfg(feature = "search_bing")] +pub use search::BingProvider; + +#[cfg(feature = "search_tavily")] +pub use search::TavilyProvider; + +#[cfg(feature = "memvid")] +pub use automation::{ + ExperienceMemory, ExperienceMemoryConfig, ExperienceOutcome, ExperienceRecord, + MemoryStats as ExperienceMemoryStats, RecalledExperience, +}; + +#[cfg(feature = "chrome")] +pub use browser::BrowserContext; + +#[cfg(feature = "chrome")] +pub use automation::{ + run_remote_multimodal_with_page, run_spawn_pages_concurrent, run_spawn_pages_with_factory, + run_spawn_pages_with_options, PageFactory, PageSetupFn, SpawnPageOptions, SpawnedPageResult, +}; + +#[cfg(feature = "webdriver")] +pub use webdriver::WebDriverContext; + +#[cfg(feature = "fs")] +pub use temp::{TempFile, TempStorage}; diff --git a/spider_agent/src/llm/mod.rs b/spider_agent/src/llm/mod.rs new file mode 100644 index 000000000..6fcb8c443 --- /dev/null +++ b/spider_agent/src/llm/mod.rs @@ -0,0 +1,209 @@ +//! LLM provider abstractions for spider_agent. + +#[cfg(feature = "openai")] +mod openai; + +#[cfg(feature = "openai")] +pub use openai::{OpenAIProvider, OpenAiApiMode}; + +use crate::error::AgentResult; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +/// LLM provider trait for abstracting different LLM APIs. +#[async_trait] +pub trait LLMProvider: Send + Sync { + /// Send a completion request and return the response text. + async fn complete( + &self, + messages: Vec, + options: &CompletionOptions, + client: &reqwest::Client, + ) -> AgentResult; + + /// Provider name for logging/debugging. + fn provider_name(&self) -> &'static str; + + /// Check if the provider is properly configured. + fn is_configured(&self) -> bool; +} + +/// A message in a conversation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Message { + /// Role: "system", "user", or "assistant". + pub role: String, + /// Message content. + pub content: MessageContent, +} + +impl Message { + /// Create a system message. + pub fn system(content: impl Into) -> Self { + Self { + role: "system".to_string(), + content: MessageContent::Text(content.into()), + } + } + + /// Create a user message. + pub fn user(content: impl Into) -> Self { + Self { + role: "user".to_string(), + content: MessageContent::Text(content.into()), + } + } + + /// Create an assistant message. + pub fn assistant(content: impl Into) -> Self { + Self { + role: "assistant".to_string(), + content: MessageContent::Text(content.into()), + } + } + + /// Create a user message with an image. + pub fn user_with_image(text: impl Into, image_base64: impl Into) -> Self { + Self { + role: "user".to_string(), + content: MessageContent::MultiPart(vec![ + ContentPart::Text { text: text.into() }, + ContentPart::ImageUrl { + image_url: ImageUrl { + url: format!("data:image/png;base64,{}", image_base64.into()), + }, + }, + ]), + } + } +} + +/// Message content - either text or multi-part. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MessageContent { + /// Plain text content. + Text(String), + /// Multi-part content (text + images). + MultiPart(Vec), +} + +impl MessageContent { + /// Get text content as a string reference. + /// + /// For multi-part content, returns the concatenated text parts. + pub fn as_text(&self) -> &str { + match self { + Self::Text(s) => s, + Self::MultiPart(parts) => { + // Return first text part, or empty string + for part in parts { + if let ContentPart::Text { text } = part { + return text; + } + } + "" + } + } + } + + /// Get the full text from all text parts. + pub fn full_text(&self) -> String { + match self { + Self::Text(s) => s.clone(), + Self::MultiPart(parts) => parts + .iter() + .filter_map(|p| { + if let ContentPart::Text { text } = p { + Some(text.as_str()) + } else { + None + } + }) + .collect::>() + .join(" "), + } + } + + /// Check if this is text-only content. + pub fn is_text(&self) -> bool { + matches!(self, Self::Text(_)) + } + + /// Check if this contains images. + pub fn has_images(&self) -> bool { + match self { + Self::Text(_) => false, + Self::MultiPart(parts) => parts + .iter() + .any(|p| matches!(p, ContentPart::ImageUrl { .. })), + } + } +} + +/// A part of multi-part content. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ContentPart { + /// Text part. + Text { text: String }, + /// Image URL part. + ImageUrl { image_url: ImageUrl }, +} + +/// Image URL for vision models. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImageUrl { + /// URL (can be data URL with base64). + pub url: String, +} + +/// Options for completion requests. +#[derive(Debug, Clone)] +pub struct CompletionOptions { + /// Temperature (0.0 - 2.0). + pub temperature: f32, + /// Max tokens to generate. + pub max_tokens: u16, + /// Request JSON output. + pub json_mode: bool, +} + +impl Default for CompletionOptions { + fn default() -> Self { + Self { + temperature: 0.1, + max_tokens: 4096, + json_mode: true, + } + } +} + +/// Response from a completion request. +#[derive(Debug, Clone)] +pub struct CompletionResponse { + /// The generated text. + pub content: String, + /// Token usage. + pub usage: TokenUsage, +} + +/// Token usage from a completion. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TokenUsage { + /// Prompt tokens. + pub prompt_tokens: u32, + /// Completion tokens. + pub completion_tokens: u32, + /// Total tokens. + pub total_tokens: u32, +} + +impl TokenUsage { + /// Accumulate usage from another. + pub fn accumulate(&mut self, other: &TokenUsage) { + self.prompt_tokens += other.prompt_tokens; + self.completion_tokens += other.completion_tokens; + self.total_tokens += other.total_tokens; + } +} diff --git a/spider_agent/src/llm/openai.rs b/spider_agent/src/llm/openai.rs new file mode 100644 index 000000000..aec188e97 --- /dev/null +++ b/spider_agent/src/llm/openai.rs @@ -0,0 +1,700 @@ +//! OpenAI-compatible LLM provider implementation. + +use super::{CompletionOptions, CompletionResponse, LLMProvider, Message, TokenUsage}; +use crate::error::{AgentError, AgentResult}; +use async_trait::async_trait; + +/// Default OpenAI base URL. +const DEFAULT_OPENAI_BASE: &str = "https://api.openai.com"; + +/// Which OpenAI API surface to target. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum OpenAiApiMode { + /// Auto-detect from the URL: `api.openai.com` → Responses, + /// everything else → Completions. This is the default. + #[default] + Auto, + /// Chat Completions API (`/v1/chat/completions`). + Completions, + /// Responses API (`/v1/responses`). + Responses, +} + +/// Resolved (non-Auto) mode used at request time. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ResolvedMode { + Completions, + Responses, +} + +/// Returns true when the URL points at OpenAI's first-party API. +fn is_openai_url(url: &str) -> bool { + // Fast byte check — avoid allocations. + url.starts_with("https://api.openai.com") || url.starts_with("http://api.openai.com") +} + +/// OpenAI-compatible LLM provider. +/// +/// Works with OpenAI API and compatible endpoints (Anthropic, local models, etc.). +/// +/// # Example +/// ```ignore +/// use spider_agent::llm::{OpenAIProvider, Message, CompletionOptions}; +/// +/// let provider = OpenAIProvider::new("sk-...", "gpt-4o"); +/// let client = reqwest::Client::new(); +/// +/// let messages = vec![ +/// Message::system("You are a helpful assistant."), +/// Message::user("What is Rust?"), +/// ]; +/// +/// let response = provider.complete(messages, &CompletionOptions::default(), &client).await?; +/// println!("{}", response.content); +/// ``` +#[derive(Debug, Clone)] +pub struct OpenAIProvider { + api_key: String, + api_url: String, + model: String, + api_mode: OpenAiApiMode, +} + +impl OpenAIProvider { + /// Create a new OpenAI provider. + /// + /// Defaults to `Auto` mode: uses the Responses API for `api.openai.com` + /// and Chat Completions for everything else. + pub fn new(api_key: impl Into, model: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: DEFAULT_OPENAI_BASE.to_string(), + model: model.into(), + api_mode: OpenAiApiMode::Auto, + } + } + + /// Use a custom API endpoint (for compatible APIs). + /// + /// In `Auto` mode the path suffix is resolved at request time based on + /// the host. In explicit `Completions` / `Responses` mode the correct + /// suffix is appended immediately if missing. + pub fn with_api_url(mut self, url: impl Into) -> Self { + let raw = url.into(); + match self.api_mode { + OpenAiApiMode::Auto => { + // Store the base; path is resolved at request time. + self.api_url = strip_known_suffixes(raw); + } + mode => { + self.api_url = normalize_api_url(raw, mode); + } + } + self + } + + /// Change the model. + pub fn with_model(mut self, model: impl Into) -> Self { + self.model = model.into(); + self + } + + /// Force the Chat Completions API (`/v1/chat/completions`). + pub fn with_completions_api(mut self) -> Self { + self.api_mode = OpenAiApiMode::Completions; + self.api_url = normalize_api_url(self.api_url, OpenAiApiMode::Completions); + self + } + + /// Force the Responses API (`/v1/responses`). + /// + /// The Responses API uses `instructions` + `input` instead of a + /// `messages` array and returns structured `output` items. + pub fn with_responses_api(mut self) -> Self { + self.api_mode = OpenAiApiMode::Responses; + self.api_url = normalize_api_url(self.api_url, OpenAiApiMode::Responses); + self + } + + /// Resolve the effective mode (never `Auto`). + fn resolved_mode(&self) -> ResolvedMode { + match self.api_mode { + OpenAiApiMode::Completions => ResolvedMode::Completions, + OpenAiApiMode::Responses => ResolvedMode::Responses, + OpenAiApiMode::Auto => { + if is_openai_url(&self.api_url) { + ResolvedMode::Responses + } else { + ResolvedMode::Completions + } + } + } + } + + /// Build the full request URL, appending the path suffix when in Auto mode. + fn request_url(&self) -> String { + match self.api_mode { + OpenAiApiMode::Auto => { + let base = self.api_url.trim_end_matches('/'); + // Already has a known endpoint suffix — use as-is. + if base.ends_with("/responses") || base.ends_with("/chat/completions") { + return base.to_string(); + } + let suffix = match self.resolved_mode() { + ResolvedMode::Responses => "/responses", + ResolvedMode::Completions => "/chat/completions", + }; + format!("{}{}", base, suffix) + } + _ => self.api_url.clone(), + } + } +} + +#[async_trait] +impl LLMProvider for OpenAIProvider { + async fn complete( + &self, + messages: Vec, + options: &CompletionOptions, + client: &reqwest::Client, + ) -> AgentResult { + let mode = self.resolved_mode(); + + let body = match mode { + ResolvedMode::Completions => build_completions_body(&self.model, &messages, options), + ResolvedMode::Responses => build_responses_body(&self.model, &messages, options), + }; + + let url = self.request_url(); + + let response = client + .post(&url) + .header("Authorization", format!("Bearer {}", self.api_key)) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(AgentError::Llm("Authentication failed".to_string())); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(AgentError::RateLimited); + } + if !status.is_success() { + let error_text = response.text().await.unwrap_or_default(); + return Err(AgentError::Llm(format!("HTTP {}: {}", status, error_text))); + } + + let json: serde_json::Value = response.json().await?; + + match mode { + ResolvedMode::Completions => parse_completions_response(json), + ResolvedMode::Responses => parse_responses_response(json), + } + } + + fn provider_name(&self) -> &'static str { + match self.resolved_mode() { + ResolvedMode::Completions => "openai", + ResolvedMode::Responses => "openai_responses", + } + } + + fn is_configured(&self) -> bool { + !self.api_key.is_empty() + } +} + +// --------------- Chat Completions helpers --------------- + +fn build_completions_body( + model: &str, + messages: &[Message], + options: &CompletionOptions, +) -> serde_json::Value { + let mut body = serde_json::json!({ + "model": model, + "messages": messages, + "temperature": options.temperature, + "max_tokens": options.max_tokens, + }); + if options.json_mode { + body["response_format"] = serde_json::json!({ "type": "json_object" }); + } + body +} + +fn parse_completions_response(json: serde_json::Value) -> AgentResult { + let content = json + .get("choices") + .and_then(|c| c.get(0)) + .and_then(|c| c.get("message")) + .and_then(|m| m.get("content")) + .and_then(|c| c.as_str()) + .ok_or_else(|| AgentError::MissingField("choices[0].message.content"))? + .to_string(); + + let usage = extract_usage(&json); + Ok(CompletionResponse { content, usage }) +} + +// --------------- Responses API helpers --------------- + +fn build_responses_body( + model: &str, + messages: &[Message], + options: &CompletionOptions, +) -> serde_json::Value { + // Separate system messages into `instructions` and the rest into `input`. + let mut instructions = String::new(); + let mut input_items = Vec::new(); + + for msg in messages { + if msg.role == "system" { + if !instructions.is_empty() { + instructions.push('\n'); + } + instructions.push_str(msg.content.as_text()); + } else { + input_items.push(serde_json::json!({ + "role": &msg.role, + "content": &msg.content, + })); + } + } + + let mut body = serde_json::json!({ + "model": model, + "input": input_items, + "temperature": options.temperature, + }); + + if !instructions.is_empty() { + body["instructions"] = serde_json::json!(instructions); + } + + if options.json_mode { + body["text"] = serde_json::json!({ + "format": { "type": "json_object" } + }); + } + + body +} + +fn parse_responses_response(json: serde_json::Value) -> AgentResult { + // Try the `output_text` shorthand first (present in newer API responses). + if let Some(text) = json.get("output_text").and_then(|v| v.as_str()) { + return Ok(CompletionResponse { + content: text.to_string(), + usage: extract_usage(&json), + }); + } + + // Fall back to walking the `output` array. + let content = json + .get("output") + .and_then(|o| o.as_array()) + .and_then(|arr| { + for item in arr { + if item.get("type").and_then(|t| t.as_str()) == Some("message") { + if let Some(parts) = item.get("content").and_then(|c| c.as_array()) { + for part in parts { + if part.get("type").and_then(|t| t.as_str()) == Some("output_text") { + return part.get("text").and_then(|t| t.as_str()); + } + } + } + } + } + None + }) + .ok_or_else(|| AgentError::MissingField("output[].content[].text"))? + .to_string(); + + Ok(CompletionResponse { + content, + usage: extract_usage(&json), + }) +} + +// --------------- Shared helpers --------------- + +fn extract_usage(json: &serde_json::Value) -> TokenUsage { + if let Some(u) = json.get("usage") { + TokenUsage { + prompt_tokens: u + .get("prompt_tokens") + .or_else(|| u.get("input_tokens")) + .and_then(|v| v.as_u64()) + .unwrap_or(0) as u32, + completion_tokens: u + .get("completion_tokens") + .or_else(|| u.get("output_tokens")) + .and_then(|v| v.as_u64()) + .unwrap_or(0) as u32, + total_tokens: u.get("total_tokens").and_then(|v| v.as_u64()).unwrap_or(0) as u32, + } + } else { + TokenUsage::default() + } +} + +/// Strip `/chat/completions` or `/responses` suffix, returning just the base. +fn strip_known_suffixes(mut url: String) -> String { + while url.ends_with('/') { + url.pop(); + } + if let Some(base) = url.strip_suffix("/chat/completions") { + url.truncate(base.len()); + } else if let Some(base) = url.strip_suffix("/responses") { + url.truncate(base.len()); + } + url +} + +/// Ensure the URL ends with the correct path for the given API mode. +/// +/// Only called with explicit `Completions` / `Responses` (never `Auto`). +/// Avoids allocation when the URL is already correct. +fn normalize_api_url(url: String, mode: OpenAiApiMode) -> String { + let mut base = strip_known_suffixes(url); + let suffix = match mode { + OpenAiApiMode::Completions | OpenAiApiMode::Auto => "/chat/completions", + OpenAiApiMode::Responses => "/responses", + }; + base.push_str(suffix); + base +} + +#[cfg(test)] +mod tests { + use super::*; + + // --------------- Constructor & config --------------- + + #[test] + fn test_openai_provider_new() { + let p = OpenAIProvider::new("sk-test", "gpt-4o"); + assert!(p.is_configured()); + assert_eq!(p.model, "gpt-4o"); + assert_eq!(p.api_mode, OpenAiApiMode::Auto); + } + + #[test] + fn test_openai_provider_empty_key_not_configured() { + assert!(!OpenAIProvider::new("", "gpt-4o").is_configured()); + } + + // --------------- Auto mode detection --------------- + + #[test] + fn test_auto_mode_openai_resolves_to_responses() { + let p = OpenAIProvider::new("sk-test", "gpt-4o"); + assert_eq!(p.resolved_mode(), ResolvedMode::Responses); + assert_eq!(p.request_url(), "https://api.openai.com/responses"); + } + + #[test] + fn test_auto_mode_custom_url_resolves_to_completions() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_api_url("https://api.xyz.com/llm/v1"); + assert_eq!(p.resolved_mode(), ResolvedMode::Completions); + assert_eq!( + p.request_url(), + "https://api.xyz.com/llm/v1/chat/completions" + ); + } + + #[test] + fn test_auto_mode_localhost_resolves_to_completions() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_api_url("https://localhost:8000"); + assert_eq!(p.resolved_mode(), ResolvedMode::Completions); + assert_eq!(p.request_url(), "https://localhost:8000/chat/completions"); + } + + #[test] + fn test_auto_mode_strips_completions_suffix_stores_base() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_api_url("https://api.xyz.com/v1/chat/completions"); + assert_eq!(p.api_url, "https://api.xyz.com/v1"); + assert_eq!(p.request_url(), "https://api.xyz.com/v1/chat/completions"); + } + + #[test] + fn test_auto_mode_strips_responses_suffix_stores_base() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_api_url("https://api.xyz.com/v1/responses"); + assert_eq!(p.api_url, "https://api.xyz.com/v1"); + } + + #[test] + fn test_auto_mode_trailing_slash() { + let p = + OpenAIProvider::new("sk-test", "gpt-4o").with_api_url("https://api.xyz.com/llm/v1/"); + assert_eq!(p.api_url, "https://api.xyz.com/llm/v1"); + } + + // --------------- Explicit Completions mode --------------- + + #[test] + fn test_explicit_completions_url() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_completions_api(); + assert_eq!(p.api_mode, OpenAiApiMode::Completions); + assert!(p.request_url().ends_with("/chat/completions")); + } + + #[test] + fn test_explicit_completions_custom_url_base() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_completions_api() + .with_api_url("https://api.xyz.com/v1"); + assert_eq!(p.request_url(), "https://api.xyz.com/v1/chat/completions"); + } + + #[test] + fn test_explicit_completions_custom_url_full() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_completions_api() + .with_api_url("https://api.xyz.com/v1/chat/completions"); + assert_eq!(p.request_url(), "https://api.xyz.com/v1/chat/completions"); + } + + // --------------- Explicit Responses mode --------------- + + #[test] + fn test_explicit_responses_url() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_responses_api(); + assert_eq!(p.api_mode, OpenAiApiMode::Responses); + assert!(p.request_url().ends_with("/responses")); + } + + #[test] + fn test_explicit_responses_custom_base() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_responses_api() + .with_api_url("https://custom.api.com/v1"); + assert_eq!(p.request_url(), "https://custom.api.com/v1/responses"); + } + + #[test] + fn test_explicit_responses_full_url() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_responses_api() + .with_api_url("https://custom.api.com/v1/responses"); + assert_eq!(p.request_url(), "https://custom.api.com/v1/responses"); + } + + #[test] + fn test_explicit_responses_switches_from_completions_url() { + let p = OpenAIProvider::new("sk-test", "gpt-4o") + .with_completions_api() + .with_api_url("https://custom.api.com/v1/chat/completions") + .with_responses_api(); + assert_eq!(p.request_url(), "https://custom.api.com/v1/responses"); + } + + // --------------- Provider name --------------- + + #[test] + fn test_provider_name_auto_openai() { + let p = OpenAIProvider::new("sk-test", "gpt-4o"); + assert_eq!(p.provider_name(), "openai_responses"); + } + + #[test] + fn test_provider_name_auto_custom() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_api_url("https://vllm.local"); + assert_eq!(p.provider_name(), "openai"); + } + + #[test] + fn test_provider_name_explicit_responses() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_responses_api(); + assert_eq!(p.provider_name(), "openai_responses"); + } + + #[test] + fn test_provider_name_explicit_completions() { + let p = OpenAIProvider::new("sk-test", "gpt-4o").with_completions_api(); + assert_eq!(p.provider_name(), "openai"); + } + + // --------------- normalize / strip helpers --------------- + + #[test] + fn test_normalize_completions_bare_host() { + assert_eq!( + normalize_api_url("https://localhost:8000".into(), OpenAiApiMode::Completions), + "https://localhost:8000/chat/completions" + ); + } + + #[test] + fn test_normalize_responses_bare_host() { + assert_eq!( + normalize_api_url("https://localhost:8000".into(), OpenAiApiMode::Responses), + "https://localhost:8000/responses" + ); + } + + #[test] + fn test_normalize_strips_completions_for_responses() { + assert_eq!( + normalize_api_url( + "https://api.example.com/v1/chat/completions".into(), + OpenAiApiMode::Responses + ), + "https://api.example.com/v1/responses" + ); + } + + #[test] + fn test_normalize_strips_responses_for_completions() { + assert_eq!( + normalize_api_url( + "https://api.example.com/v1/responses".into(), + OpenAiApiMode::Completions + ), + "https://api.example.com/v1/chat/completions" + ); + } + + #[test] + fn test_strip_known_suffixes_completions() { + assert_eq!( + strip_known_suffixes("https://api.example.com/v1/chat/completions".into()), + "https://api.example.com/v1" + ); + } + + #[test] + fn test_strip_known_suffixes_responses() { + assert_eq!( + strip_known_suffixes("https://api.example.com/v1/responses".into()), + "https://api.example.com/v1" + ); + } + + #[test] + fn test_strip_known_suffixes_no_suffix() { + assert_eq!( + strip_known_suffixes("https://api.example.com/v1".into()), + "https://api.example.com/v1" + ); + } + + #[test] + fn test_is_openai_url_true() { + assert!(is_openai_url("https://api.openai.com/v1/responses")); + assert!(is_openai_url("https://api.openai.com")); + } + + #[test] + fn test_is_openai_url_false() { + assert!(!is_openai_url("https://api.xyz.com/v1")); + assert!(!is_openai_url("https://localhost:8000")); + assert!(!is_openai_url("https://openai.example.com")); + } + + // --------------- Request body builders --------------- + + #[test] + fn test_build_responses_body_system_becomes_instructions() { + let messages = vec![Message::system("You are helpful."), Message::user("Hello")]; + let body = build_responses_body("gpt-4o", &messages, &CompletionOptions::default()); + assert_eq!(body["instructions"], "You are helpful."); + assert!(body.get("messages").is_none()); + let input = body["input"].as_array().expect("input should be array"); + assert_eq!(input.len(), 1); + assert_eq!(input[0]["role"], "user"); + } + + #[test] + fn test_build_responses_body_no_system() { + let messages = vec![Message::user("Hello")]; + let body = build_responses_body("gpt-4o", &messages, &CompletionOptions::default()); + assert!(body.get("instructions").is_none()); + assert_eq!(body["input"].as_array().expect("array").len(), 1); + } + + #[test] + fn test_build_responses_body_json_mode() { + let mut opts = CompletionOptions::default(); + opts.json_mode = true; + let body = build_responses_body("gpt-4o", &[Message::user("Hi")], &opts); + assert_eq!(body["text"]["format"]["type"], "json_object"); + } + + #[test] + fn test_build_completions_body_json_mode() { + let mut opts = CompletionOptions::default(); + opts.json_mode = true; + let body = build_completions_body("gpt-4o", &[Message::user("Hi")], &opts); + assert_eq!(body["response_format"]["type"], "json_object"); + } + + // --------------- Response parsers --------------- + + #[test] + fn test_parse_responses_output_text_shorthand() { + let json = serde_json::json!({ + "output_text": "Hello world", + "usage": { "input_tokens": 10, "output_tokens": 5, "total_tokens": 15 } + }); + let resp = parse_responses_response(json).expect("should parse"); + assert_eq!(resp.content, "Hello world"); + assert_eq!(resp.usage.prompt_tokens, 10); + assert_eq!(resp.usage.completion_tokens, 5); + assert_eq!(resp.usage.total_tokens, 15); + } + + #[test] + fn test_parse_responses_output_array() { + let json = serde_json::json!({ + "output": [{ "type": "message", "content": [{ "type": "output_text", "text": "Extracted" }] }] + }); + let resp = parse_responses_response(json).expect("should parse"); + assert_eq!(resp.content, "Extracted"); + } + + #[test] + fn test_parse_responses_missing_output() { + let err = parse_responses_response(serde_json::json!({})).unwrap_err(); + assert!(format!("{}", err).contains("Missing field")); + } + + // --------------- Usage extraction --------------- + + #[test] + fn test_extract_usage_completions_keys() { + let json = serde_json::json!({ + "usage": { "prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30 } + }); + let u = extract_usage(&json); + assert_eq!( + (u.prompt_tokens, u.completion_tokens, u.total_tokens), + (10, 20, 30) + ); + } + + #[test] + fn test_extract_usage_responses_keys() { + let json = serde_json::json!({ + "usage": { "input_tokens": 10, "output_tokens": 20, "total_tokens": 30 } + }); + let u = extract_usage(&json); + assert_eq!((u.prompt_tokens, u.completion_tokens), (10, 20)); + } + + #[test] + fn test_extract_usage_missing() { + let u = extract_usage(&serde_json::json!({})); + assert_eq!( + (u.prompt_tokens, u.completion_tokens, u.total_tokens), + (0, 0, 0) + ); + } +} diff --git a/spider_agent/src/memory.rs b/spider_agent/src/memory.rs new file mode 100644 index 000000000..fa0a1997e --- /dev/null +++ b/spider_agent/src/memory.rs @@ -0,0 +1,528 @@ +//! Session memory for spider_agent. +//! +//! Uses DashMap for lock-free concurrent access. +//! +//! # Features +//! - **Key-Value Store**: Lock-free concurrent storage for arbitrary JSON values +//! - **URL History**: Track visited URLs for navigation context +//! - **Action History**: Record actions taken for debugging and context +//! - **Extraction History**: Accumulate extracted data across pages +//! +//! Compatible with spider's AutomationMemory patterns while using +//! DashMap for optimal concurrent performance. + +use dashmap::DashMap; +use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +/// Maximum number of actions to keep in history. +const MAX_ACTION_HISTORY: usize = 50; +/// Maximum number of URLs to keep in history. +const MAX_URL_HISTORY: usize = 100; +/// Maximum number of extractions to keep. +const MAX_EXTRACTIONS: usize = 50; + +/// Session memory for storing state across operations. +/// +/// Uses DashMap internally for lock-free concurrent reads and writes. +/// This is optimal for high-concurrency scenarios. +/// +/// # Example +/// ``` +/// use spider_agent::AgentMemory; +/// +/// let memory = AgentMemory::new(); +/// +/// // Key-value storage +/// memory.set("user_id", serde_json::json!("12345")); +/// +/// // URL tracking +/// memory.add_visited_url("https://example.com"); +/// +/// // Action history +/// memory.add_action("Searched for 'rust frameworks'"); +/// +/// // Extraction history +/// memory.add_extraction(serde_json::json!({"title": "Example"})); +/// +/// // Generate context for LLM +/// let context = memory.to_context_string(); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct AgentMemory { + /// Lock-free concurrent key-value store. + data: Arc>, + /// History of visited URLs (most recent last). + visited_urls: Arc>>, + /// Brief summary of recent actions (most recent last). + action_history: Arc>>, + /// History of extracted data from pages (most recent last). + extractions: Arc>>, +} + +impl AgentMemory { + /// Create a new empty memory. + pub fn new() -> Self { + Self { + data: Arc::new(DashMap::new()), + visited_urls: Arc::new(RwLock::new(Vec::new())), + action_history: Arc::new(RwLock::new(Vec::new())), + extractions: Arc::new(RwLock::new(Vec::new())), + } + } + + /// Create memory with pre-allocated capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + data: Arc::new(DashMap::with_capacity(capacity)), + visited_urls: Arc::new(RwLock::new(Vec::with_capacity(MAX_URL_HISTORY))), + action_history: Arc::new(RwLock::new(Vec::with_capacity(MAX_ACTION_HISTORY))), + extractions: Arc::new(RwLock::new(Vec::with_capacity(MAX_EXTRACTIONS))), + } + } + + // ========== Key-Value Store ========== + + /// Get a value from memory. + /// + /// Returns a clone of the value to avoid holding refs across await points. + pub fn get(&self, key: &str) -> Option { + self.data.get(key).map(|v| v.value().clone()) + } + + /// Set a value in memory. + pub fn set(&self, key: impl Into, value: serde_json::Value) { + self.data.insert(key.into(), value); + } + + /// Remove a value from memory. + pub fn remove(&self, key: &str) -> Option { + self.data.remove(key).map(|(_, v)| v) + } + + /// Clear all key-value data. + pub fn clear(&self) { + self.data.clear(); + } + + /// Check if memory contains a key. + pub fn contains(&self, key: &str) -> bool { + self.data.contains_key(key) + } + + /// Get number of key-value entries. + pub fn len(&self) -> usize { + self.data.len() + } + + /// Check if key-value store is empty. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Get a typed value from memory. + pub fn get_as Deserialize<'de>>(&self, key: &str) -> Option { + self.data + .get(key) + .and_then(|v| serde_json::from_value(v.value().clone()).ok()) + } + + /// Set a typed value in memory. + pub fn set_value(&self, key: impl Into, value: &T) { + if let Ok(json) = serde_json::to_value(value) { + self.data.insert(key.into(), json); + } + } + + /// Update a value atomically using a closure. + /// + /// The closure receives the current value (if any) and returns the new value. + pub fn update(&self, key: impl Into, f: F) + where + F: FnOnce(Option<&serde_json::Value>) -> serde_json::Value, + { + let key = key.into(); + let new_value = f(self.data.get(&key).as_deref()); + self.data.insert(key, new_value); + } + + /// Get or insert a value. + pub fn get_or_insert( + &self, + key: impl Into, + default: serde_json::Value, + ) -> serde_json::Value { + self.data + .entry(key.into()) + .or_insert(default) + .value() + .clone() + } + + // ========== URL History ========== + + /// Record a visited URL. + /// + /// Keeps the most recent URLs up to the limit. + pub fn add_visited_url(&self, url: impl Into) { + let mut urls = self.visited_urls.write(); + urls.push(url.into()); + if urls.len() > MAX_URL_HISTORY { + urls.remove(0); + } + } + + /// Get the list of visited URLs. + pub fn visited_urls(&self) -> Vec { + self.visited_urls.read().clone() + } + + /// Get the last N visited URLs. + pub fn recent_urls(&self, n: usize) -> Vec { + let urls = self.visited_urls.read(); + urls.iter().rev().take(n).cloned().collect() + } + + /// Check if a URL has been visited. + pub fn has_visited(&self, url: &str) -> bool { + self.visited_urls.read().iter().any(|u| u == url) + } + + /// Clear URL history. + pub fn clear_urls(&self) { + self.visited_urls.write().clear(); + } + + // ========== Action History ========== + + /// Record an action summary. + /// + /// Keeps the most recent actions up to the limit. + pub fn add_action(&self, action: impl Into) { + let mut actions = self.action_history.write(); + actions.push(action.into()); + if actions.len() > MAX_ACTION_HISTORY { + actions.remove(0); + } + } + + /// Get the list of actions. + pub fn action_history(&self) -> Vec { + self.action_history.read().clone() + } + + /// Get the last N actions. + pub fn recent_actions(&self, n: usize) -> Vec { + let actions = self.action_history.read(); + actions.iter().rev().take(n).cloned().collect() + } + + /// Clear action history. + pub fn clear_actions(&self) { + self.action_history.write().clear(); + } + + // ========== Extraction History ========== + + /// Add an extracted value to history. + /// + /// Keeps the most recent extractions up to the limit. + pub fn add_extraction(&self, data: serde_json::Value) { + let mut extractions = self.extractions.write(); + extractions.push(data); + if extractions.len() > MAX_EXTRACTIONS { + extractions.remove(0); + } + } + + /// Get all extractions. + pub fn extractions(&self) -> Vec { + self.extractions.read().clone() + } + + /// Get the last N extractions. + pub fn recent_extractions(&self, n: usize) -> Vec { + let extractions = self.extractions.read(); + extractions.iter().rev().take(n).cloned().collect() + } + + /// Clear extraction history. + pub fn clear_extractions(&self) { + self.extractions.write().clear(); + } + + // ========== Bulk Operations ========== + + /// Clear all history (URLs, actions, extractions) but keep key-value store. + pub fn clear_history(&self) { + self.visited_urls.write().clear(); + self.action_history.write().clear(); + self.extractions.write().clear(); + } + + /// Clear everything including key-value store and all history. + pub fn clear_all(&self) { + self.data.clear(); + self.visited_urls.write().clear(); + self.action_history.write().clear(); + self.extractions.write().clear(); + } + + /// Check if all memory is empty (store + all history). + pub fn is_all_empty(&self) -> bool { + self.data.is_empty() + && self.visited_urls.read().is_empty() + && self.action_history.read().is_empty() + && self.extractions.read().is_empty() + } + + // ========== Context Generation ========== + + /// Generate a context string for inclusion in LLM prompts. + /// + /// This provides the LLM with session context including: + /// - Key-value store contents + /// - Recent URLs visited + /// - Recent actions taken + /// - Recent extractions + pub fn to_context_string(&self) -> String { + if self.is_all_empty() { + return String::new(); + } + + let mut parts = Vec::new(); + + // Key-value store + if !self.data.is_empty() { + let store: std::collections::HashMap<_, _> = self + .data + .iter() + .map(|r| (r.key().clone(), r.value().clone())) + .collect(); + if let Ok(json) = serde_json::to_string_pretty(&store) { + parts.push(format!("## Memory Store\n```json\n{}\n```", json)); + } + } + + // Recent URLs + let urls = self.visited_urls.read(); + if !urls.is_empty() { + let recent: Vec<_> = urls.iter().rev().take(10).collect(); + let url_list: String = recent + .iter() + .rev() + .enumerate() + .map(|(i, u)| format!("{}. {}", i + 1, u)) + .collect::>() + .join("\n"); + parts.push(format!( + "## Recent URLs (last {})\n{}", + recent.len(), + url_list + )); + } + drop(urls); + + // Recent extractions + let extractions = self.extractions.read(); + if !extractions.is_empty() { + let recent: Vec<_> = extractions.iter().rev().take(5).collect(); + let json_strs: Vec<_> = recent + .iter() + .rev() + .filter_map(|v| serde_json::to_string(v).ok()) + .collect(); + parts.push(format!( + "## Recent Extractions (last {})\n{}", + json_strs.len(), + json_strs.join("\n") + )); + } + drop(extractions); + + // Recent actions + let actions = self.action_history.read(); + if !actions.is_empty() { + let recent: Vec<_> = actions.iter().rev().take(10).collect(); + let action_list: String = recent + .iter() + .rev() + .enumerate() + .map(|(i, a)| format!("{}. {}", i + 1, a)) + .collect::>() + .join("\n"); + parts.push(format!( + "## Recent Actions (last {})\n{}", + recent.len(), + action_list + )); + } + drop(actions); + + parts.join("\n\n") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_basic() { + let memory = AgentMemory::new(); + + memory.set("key1", serde_json::json!("value1")); + memory.set("key2", serde_json::json!(42)); + + assert_eq!(memory.get("key1"), Some(serde_json::json!("value1"))); + assert_eq!(memory.get("key2"), Some(serde_json::json!(42))); + assert_eq!(memory.get("key3"), None); + assert_eq!(memory.len(), 2); + } + + #[test] + fn test_memory_typed() { + let memory = AgentMemory::new(); + + memory.set_value("name", &"Alice".to_string()); + memory.set_value("age", &30u32); + + assert_eq!(memory.get_as::("name"), Some("Alice".to_string())); + assert_eq!(memory.get_as::("age"), Some(30)); + } + + #[test] + fn test_memory_clear() { + let memory = AgentMemory::new(); + + memory.set("key1", serde_json::json!("value1")); + memory.set("key2", serde_json::json!("value2")); + + assert_eq!(memory.len(), 2); + + memory.clear(); + + assert!(memory.is_empty()); + } + + #[test] + fn test_memory_update() { + let memory = AgentMemory::new(); + + memory.set("counter", serde_json::json!(0)); + + memory.update("counter", |v| { + let current = v.and_then(|v| v.as_i64()).unwrap_or(0); + serde_json::json!(current + 1) + }); + + assert_eq!(memory.get("counter"), Some(serde_json::json!(1))); + } + + #[test] + fn test_memory_get_or_insert() { + let memory = AgentMemory::new(); + + let value = memory.get_or_insert("key", serde_json::json!("default")); + assert_eq!(value, serde_json::json!("default")); + + // Should return existing value + memory.set("key", serde_json::json!("updated")); + let value = memory.get_or_insert("key", serde_json::json!("other")); + assert_eq!(value, serde_json::json!("updated")); + } + + #[test] + fn test_memory_concurrent_clone() { + let memory = AgentMemory::new(); + let memory2 = memory.clone(); + + memory.set("key", serde_json::json!("value")); + + // Clone shares the same underlying data + assert_eq!(memory2.get("key"), Some(serde_json::json!("value"))); + } + + #[test] + fn test_memory_url_history() { + let memory = AgentMemory::new(); + + memory.add_visited_url("https://example.com"); + memory.add_visited_url("https://example.com/page1"); + memory.add_visited_url("https://example.com/page2"); + + assert!(memory.has_visited("https://example.com")); + assert!(!memory.has_visited("https://other.com")); + + let recent = memory.recent_urls(2); + assert_eq!(recent.len(), 2); + assert_eq!(recent[0], "https://example.com/page2"); + assert_eq!(recent[1], "https://example.com/page1"); + + let all = memory.visited_urls(); + assert_eq!(all.len(), 3); + } + + #[test] + fn test_memory_action_history() { + let memory = AgentMemory::new(); + + memory.add_action("Searched for 'rust'"); + memory.add_action("Clicked search button"); + memory.add_action("Extracted results"); + + let recent = memory.recent_actions(2); + assert_eq!(recent.len(), 2); + assert_eq!(recent[0], "Extracted results"); + assert_eq!(recent[1], "Clicked search button"); + } + + #[test] + fn test_memory_extractions() { + let memory = AgentMemory::new(); + + memory.add_extraction(serde_json::json!({"title": "Page 1"})); + memory.add_extraction(serde_json::json!({"title": "Page 2"})); + + let extractions = memory.extractions(); + assert_eq!(extractions.len(), 2); + + let recent = memory.recent_extractions(1); + assert_eq!(recent[0]["title"], "Page 2"); + } + + #[test] + fn test_memory_clear_all() { + let memory = AgentMemory::new(); + + memory.set("key", serde_json::json!("value")); + memory.add_visited_url("https://example.com"); + memory.add_action("Test action"); + memory.add_extraction(serde_json::json!({"data": "test"})); + + assert!(!memory.is_all_empty()); + + memory.clear_all(); + + assert!(memory.is_all_empty()); + } + + #[test] + fn test_memory_context_string() { + let memory = AgentMemory::new(); + + memory.set("user_id", serde_json::json!("123")); + memory.add_visited_url("https://example.com"); + memory.add_action("Logged in"); + + let context = memory.to_context_string(); + + assert!(context.contains("Memory Store")); + assert!(context.contains("user_id")); + assert!(context.contains("Recent URLs")); + assert!(context.contains("example.com")); + assert!(context.contains("Recent Actions")); + assert!(context.contains("Logged in")); + } +} diff --git a/spider_agent/src/search/bing.rs b/spider_agent/src/search/bing.rs new file mode 100644 index 000000000..6a0038a06 --- /dev/null +++ b/spider_agent/src/search/bing.rs @@ -0,0 +1,176 @@ +//! Bing Web Search API provider implementation. +//! +//! Microsoft Bing provides web search via Azure Cognitive Services. + +use super::{SearchError, SearchProvider, SearchResult, SearchResults}; +use crate::config::SearchOptions; +use async_trait::async_trait; + +/// Default Bing Search API endpoint. +const DEFAULT_API_URL: &str = "https://api.bing.microsoft.com/v7.0/search"; + +/// Bing Web Search API provider. +/// +/// Provides access to Microsoft Bing web search via Azure Cognitive Services. +/// +/// # Example +/// ```ignore +/// use spider_agent::search::BingProvider; +/// use spider_agent::config::SearchOptions; +/// +/// let provider = BingProvider::new("your-api-key"); +/// let client = reqwest::Client::new(); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), &client).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct BingProvider { + api_key: String, + api_url: Option, +} + +impl BingProvider { + /// Create a new Bing provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +#[async_trait] +impl SearchProvider for BingProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: &reqwest::Client, + ) -> Result { + // Build query parameters + let mut params = vec![("q", query.to_string())]; + + if let Some(limit) = options.limit { + params.push(("count", limit.min(50).to_string())); + } + + if let Some(ref country) = options.country { + params.push(("cc", country.clone())); + } + + if let Some(ref language) = options.language { + params.push(("setLang", language.clone())); + } + + let response = client + .get(self.endpoint()) + .header("Ocp-Apim-Subscription-Key", &self.api_key) + .query(¶ms) + .send() + .await + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Bing API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract web pages + let mut results = SearchResults::new(query); + + if let Some(pages) = json + .get("webPages") + .and_then(|v| v.get("value")) + .and_then(|v| v.as_array()) + { + for (i, item) in pages.iter().enumerate() { + let title = item + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("snippet").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(date) = item.get("dateLastCrawled").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Extract total results + if let Some(total) = json + .get("webPages") + .and_then(|v| v.get("totalEstimatedMatches")) + .and_then(|v| v.as_u64()) + { + results.total_results = Some(total); + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "bing" + } + + fn is_configured(&self) -> bool { + !self.api_key.is_empty() || self.api_url.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bing_provider_new() { + let provider = BingProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + assert!(provider.is_configured()); + } + + #[test] + fn test_bing_provider_custom_url() { + let provider = BingProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider_agent/src/search/brave.rs b/spider_agent/src/search/brave.rs new file mode 100644 index 000000000..90cae06f0 --- /dev/null +++ b/spider_agent/src/search/brave.rs @@ -0,0 +1,168 @@ +//! Brave Search API provider implementation. +//! +//! Brave provides privacy-focused web search with good quality results. + +use super::{SearchError, SearchProvider, SearchResult, SearchResults}; +use crate::config::SearchOptions; +use async_trait::async_trait; + +/// Default Brave Search API endpoint. +const DEFAULT_API_URL: &str = "https://api.search.brave.com/res/v1/web/search"; + +/// Brave Search API provider. +/// +/// Provides access to Brave's privacy-focused web search API. +/// +/// # Example +/// ```ignore +/// use spider_agent::search::BraveProvider; +/// use spider_agent::config::SearchOptions; +/// +/// let provider = BraveProvider::new("your-api-key"); +/// let client = reqwest::Client::new(); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), &client).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct BraveProvider { + api_key: String, + api_url: Option, +} + +impl BraveProvider { + /// Create a new Brave provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +#[async_trait] +impl SearchProvider for BraveProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: &reqwest::Client, + ) -> Result { + // Build query parameters + let mut params = vec![("q", query.to_string())]; + + if let Some(limit) = options.limit { + params.push(("count", limit.min(20).to_string())); + } + + if let Some(ref country) = options.country { + params.push(("country", country.clone())); + } + + if let Some(ref language) = options.language { + params.push(("search_lang", language.clone())); + } + + let response = client + .get(self.endpoint()) + .header("X-Subscription-Token", &self.api_key) + .header("Accept", "application/json") + .query(¶ms) + .send() + .await + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Brave API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract web results + let mut results = SearchResults::new(query); + + if let Some(web) = json + .get("web") + .and_then(|v| v.get("results")) + .and_then(|v| v.as_array()) + { + for (i, item) in web.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("description").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(age) = item.get("age").and_then(|v| v.as_str()) { + result = result.with_date(age); + } + + results.push(result); + } + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "brave" + } + + fn is_configured(&self) -> bool { + !self.api_key.is_empty() || self.api_url.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_brave_provider_new() { + let provider = BraveProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + assert!(provider.is_configured()); + } + + #[test] + fn test_brave_provider_custom_url() { + let provider = BraveProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider_agent/src/search/mod.rs b/spider_agent/src/search/mod.rs new file mode 100644 index 000000000..56929659b --- /dev/null +++ b/spider_agent/src/search/mod.rs @@ -0,0 +1,175 @@ +//! Web search integration for spider_agent. +//! +//! This module provides search provider abstractions and implementations +//! for integrating web search APIs (Serper, Brave, Bing, Tavily). + +#[cfg(feature = "search_bing")] +mod bing; +#[cfg(feature = "search_brave")] +mod brave; +#[cfg(feature = "search_serper")] +mod serper; +#[cfg(feature = "search_tavily")] +mod tavily; + +#[cfg(feature = "search_bing")] +pub use bing::BingProvider; +#[cfg(feature = "search_brave")] +pub use brave::BraveProvider; +#[cfg(feature = "search_serper")] +pub use serper::SerperProvider; +#[cfg(feature = "search_tavily")] +pub use tavily::TavilyProvider; + +use crate::config::SearchOptions; +use crate::error::SearchError; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +/// Search provider trait for abstracting different search APIs. +#[async_trait] +pub trait SearchProvider: Send + Sync { + /// Execute a search query and return results. + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: &reqwest::Client, + ) -> Result; + + /// Provider name for logging/debugging. + fn provider_name(&self) -> &'static str; + + /// Check if the provider is properly configured. + fn is_configured(&self) -> bool; +} + +/// Unified search result format. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SearchResults { + /// The original query. + pub query: String, + /// List of organic results. + pub results: Vec, + /// Total result count (if available from provider). + pub total_results: Option, + /// Provider-specific metadata. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, +} + +impl SearchResults { + /// Create new search results. + pub fn new(query: impl Into) -> Self { + Self { + query: query.into(), + results: Vec::new(), + total_results: None, + metadata: None, + } + } + + /// Add a result. + pub fn push(&mut self, result: SearchResult) { + self.results.push(result); + } + + /// Get URLs from results. + pub fn urls(&self) -> Vec<&str> { + self.results.iter().map(|r| r.url.as_str()).collect() + } + + /// Check if results are empty. + pub fn is_empty(&self) -> bool { + self.results.is_empty() + } + + /// Get number of results. + pub fn len(&self) -> usize { + self.results.len() + } +} + +/// Individual search result. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SearchResult { + /// Result title. + pub title: String, + /// Result URL. + pub url: String, + /// Snippet/description. + #[serde(skip_serializing_if = "Option::is_none")] + pub snippet: Option, + /// Publication/index date. + #[serde(skip_serializing_if = "Option::is_none")] + pub date: Option, + /// Result position (1-indexed). + pub position: usize, + /// Relevance score (provider-dependent). + #[serde(skip_serializing_if = "Option::is_none")] + pub score: Option, +} + +impl SearchResult { + /// Create a new search result. + pub fn new(title: impl Into, url: impl Into, position: usize) -> Self { + Self { + title: title.into(), + url: url.into(), + snippet: None, + date: None, + position, + score: None, + } + } + + /// Set snippet/description. + pub fn with_snippet(mut self, snippet: impl Into) -> Self { + self.snippet = Some(snippet.into()); + self + } + + /// Set date. + pub fn with_date(mut self, date: impl Into) -> Self { + self.date = Some(date.into()); + self + } + + /// Set relevance score. + pub fn with_score(mut self, score: f32) -> Self { + self.score = Some(score); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_results() { + let mut results = SearchResults::new("test query"); + results.push(SearchResult::new("Title 1", "https://example1.com", 1)); + results.push(SearchResult::new("Title 2", "https://example2.com", 2)); + + assert_eq!(results.len(), 2); + assert!(!results.is_empty()); + assert_eq!( + results.urls(), + vec!["https://example1.com", "https://example2.com"] + ); + } + + #[test] + fn test_search_result_builder() { + let result = SearchResult::new("Test Title", "https://example.com", 1) + .with_snippet("Test snippet") + .with_score(0.95); + + assert_eq!(result.title, "Test Title"); + assert_eq!(result.url, "https://example.com"); + assert_eq!(result.position, 1); + assert_eq!(result.snippet.as_deref(), Some("Test snippet")); + assert_eq!(result.score, Some(0.95)); + } +} diff --git a/spider_agent/src/search/serper.rs b/spider_agent/src/search/serper.rs new file mode 100644 index 000000000..ebcfac381 --- /dev/null +++ b/spider_agent/src/search/serper.rs @@ -0,0 +1,206 @@ +//! Serper.dev search provider implementation. +//! +//! Serper provides high-quality Google SERP API access. + +use super::{SearchError, SearchProvider, SearchResult, SearchResults}; +use crate::config::SearchOptions; +use async_trait::async_trait; + +/// Default Serper API endpoint. +const DEFAULT_API_URL: &str = "https://google.serper.dev/search"; + +/// Serper.dev search provider. +/// +/// Provides access to Google search results via the Serper API. +/// +/// # Example +/// ```ignore +/// use spider_agent::search::SerperProvider; +/// use spider_agent::config::SearchOptions; +/// +/// let provider = SerperProvider::new("your-api-key"); +/// let client = reqwest::Client::new(); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), &client).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct SerperProvider { + api_key: String, + api_url: Option, +} + +impl SerperProvider { + /// Create a new Serper provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + } + } + + /// Use a custom API endpoint. + /// + /// This is useful for self-hosted or alternative Serper-compatible APIs. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +#[async_trait] +impl SearchProvider for SerperProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: &reqwest::Client, + ) -> Result { + // Build request body + let mut body = serde_json::json!({ + "q": query + }); + + if let Some(limit) = options.limit { + body["num"] = serde_json::json!(limit.min(100)); + } + + if let Some(ref country) = options.country { + body["gl"] = serde_json::json!(country); + } + + if let Some(ref language) = options.language { + body["hl"] = serde_json::json!(language); + } + + // Build the query with site filter if present + let query_with_filter = if let Some(ref sites) = options.site_filter { + let site_query = sites + .iter() + .map(|s| format!("site:{}", s)) + .collect::>() + .join(" OR "); + format!("{} ({})", query, site_query) + } else { + query.to_string() + }; + body["q"] = serde_json::json!(query_with_filter); + + let response = client + .post(self.endpoint()) + .header("X-API-KEY", &self.api_key) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + .map_err(|e| { + if e.is_timeout() { + SearchError::RequestFailed("Request timed out".to_string()) + } else if e.is_connect() { + SearchError::RequestFailed("Connection failed".to_string()) + } else { + SearchError::RequestFailed(e.to_string()) + } + })?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Serper API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract organic results + let mut results = SearchResults::new(query); + + if let Some(organic) = json.get("organic").and_then(|v| v.as_array()) { + for (i, item) in organic.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item + .get("link") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("snippet").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + if let Some(date) = item.get("date").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Extract total results if available + if let Some(info) = json.get("searchInformation") { + if let Some(total) = info.get("totalResults").and_then(|v| v.as_str()) { + results.total_results = total.parse().ok(); + } + } + + // Store raw metadata + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "serper" + } + + fn is_configured(&self) -> bool { + !self.api_key.is_empty() || self.api_url.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serper_provider_new() { + let provider = SerperProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + assert!(provider.is_configured()); + } + + #[test] + fn test_serper_provider_custom_url() { + let provider = + SerperProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } + + #[test] + fn test_serper_provider_empty_key() { + let provider = SerperProvider::new(""); + assert!(!provider.is_configured()); + } +} diff --git a/spider_agent/src/search/tavily.rs b/spider_agent/src/search/tavily.rs new file mode 100644 index 000000000..8603e017a --- /dev/null +++ b/spider_agent/src/search/tavily.rs @@ -0,0 +1,190 @@ +//! Tavily AI Search API provider implementation. +//! +//! Tavily provides AI-optimized search designed for LLM applications. + +use super::{SearchError, SearchProvider, SearchResult, SearchResults}; +use crate::config::SearchOptions; +use async_trait::async_trait; + +/// Default Tavily API endpoint. +const DEFAULT_API_URL: &str = "https://api.tavily.com/search"; + +/// Tavily AI Search API provider. +/// +/// Provides access to Tavily's AI-optimized search API. +/// +/// # Example +/// ```ignore +/// use spider_agent::search::TavilyProvider; +/// use spider_agent::config::SearchOptions; +/// +/// let provider = TavilyProvider::new("your-api-key"); +/// let client = reqwest::Client::new(); +/// let results = provider.search("rust web crawler", &SearchOptions::default(), &client).await?; +/// ``` +#[derive(Debug, Clone)] +pub struct TavilyProvider { + api_key: String, + api_url: Option, + /// Search depth: "basic" or "advanced". + search_depth: String, +} + +impl TavilyProvider { + /// Create a new Tavily provider with the given API key. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + api_url: None, + search_depth: "basic".to_string(), + } + } + + /// Use a custom API endpoint. + pub fn with_api_url(mut self, url: impl Into) -> Self { + self.api_url = Some(url.into()); + self + } + + /// Set search depth to "advanced" for more thorough results. + pub fn with_advanced_search(mut self) -> Self { + self.search_depth = "advanced".to_string(); + self + } + + /// Get the API endpoint URL. + fn endpoint(&self) -> &str { + self.api_url.as_deref().unwrap_or(DEFAULT_API_URL) + } +} + +#[async_trait] +impl SearchProvider for TavilyProvider { + async fn search( + &self, + query: &str, + options: &SearchOptions, + client: &reqwest::Client, + ) -> Result { + // Build request body + let mut body = serde_json::json!({ + "api_key": &self.api_key, + "query": query, + "search_depth": &self.search_depth + }); + + if let Some(limit) = options.limit { + body["max_results"] = serde_json::json!(limit.min(10)); + } + + // Tavily supports include/exclude domains + if let Some(ref sites) = options.site_filter { + body["include_domains"] = serde_json::json!(sites); + } + + if let Some(ref exclude) = options.exclude_domains { + body["exclude_domains"] = serde_json::json!(exclude); + } + + let response = client + .post(self.endpoint()) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await + .map_err(|e| SearchError::RequestFailed(e.to_string()))?; + + let status = response.status(); + if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN { + return Err(SearchError::AuthenticationFailed); + } + if status == reqwest::StatusCode::TOO_MANY_REQUESTS { + return Err(SearchError::RateLimited); + } + if !status.is_success() { + return Err(SearchError::ProviderError(format!( + "HTTP {} from Tavily API", + status + ))); + } + + // Parse response + let json: serde_json::Value = response + .json() + .await + .map_err(|e| SearchError::ProviderError(format!("Failed to parse response: {}", e)))?; + + // Extract results + let mut results = SearchResults::new(query); + + if let Some(items) = json.get("results").and_then(|v| v.as_array()) { + for (i, item) in items.iter().enumerate() { + let title = item + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + let url = item.get("url").and_then(|v| v.as_str()).unwrap_or_default(); + + if url.is_empty() { + continue; + } + + let mut result = SearchResult::new(title, url, i + 1); + + if let Some(snippet) = item.get("content").and_then(|v| v.as_str()) { + result = result.with_snippet(snippet); + } + + // Tavily provides a relevance score + if let Some(score) = item.get("score").and_then(|v| v.as_f64()) { + result = result.with_score(score as f32); + } + + if let Some(date) = item.get("published_date").and_then(|v| v.as_str()) { + result = result.with_date(date); + } + + results.push(result); + } + } + + // Store raw metadata (includes Tavily's AI-generated answer if available) + results.metadata = Some(json); + + Ok(results) + } + + fn provider_name(&self) -> &'static str { + "tavily" + } + + fn is_configured(&self) -> bool { + !self.api_key.is_empty() || self.api_url.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tavily_provider_new() { + let provider = TavilyProvider::new("test-key"); + assert_eq!(provider.endpoint(), DEFAULT_API_URL); + assert_eq!(provider.search_depth, "basic"); + assert!(provider.is_configured()); + } + + #[test] + fn test_tavily_provider_advanced() { + let provider = TavilyProvider::new("test-key").with_advanced_search(); + assert_eq!(provider.search_depth, "advanced"); + } + + #[test] + fn test_tavily_provider_custom_url() { + let provider = + TavilyProvider::new("test-key").with_api_url("https://custom.api.com/search"); + assert_eq!(provider.endpoint(), "https://custom.api.com/search"); + } +} diff --git a/spider_agent/src/temp.rs b/spider_agent/src/temp.rs new file mode 100644 index 000000000..526b15b33 --- /dev/null +++ b/spider_agent/src/temp.rs @@ -0,0 +1,265 @@ +//! Temporary filesystem utilities for large operations. +//! +//! Provides utilities for storing large data in temporary files +//! to reduce memory usage during agent operations. + +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use tempfile::{NamedTempFile, TempDir}; + +/// Temporary storage for large data operations. +/// +/// Uses the filesystem to store data that would otherwise +/// consume too much memory. +#[derive(Debug)] +pub struct TempStorage { + /// The temporary directory for this storage instance. + dir: TempDir, +} + +impl TempStorage { + /// Create a new temporary storage instance. + pub fn new() -> io::Result { + let dir = TempDir::new()?; + Ok(Self { dir }) + } + + /// Create temporary storage inside a specific parent directory. + pub fn new_in(parent: &Path) -> io::Result { + let dir = TempDir::new_in(parent)?; + Ok(Self { dir }) + } + + /// Create temporary storage with a custom prefix. + pub fn with_prefix(prefix: &str) -> io::Result { + let dir = TempDir::with_prefix(prefix)?; + Ok(Self { dir }) + } + + /// Get the path to the temporary directory. + pub fn path(&self) -> &Path { + self.dir.path() + } + + /// Store bytes in a temporary file. + pub fn store_bytes(&self, name: &str, data: &[u8]) -> io::Result { + let path = self.dir.path().join(name); + std::fs::write(&path, data)?; + Ok(path) + } + + /// Store string data in a temporary file. + pub fn store_string(&self, name: &str, data: &str) -> io::Result { + self.store_bytes(name, data.as_bytes()) + } + + /// Store JSON data in a temporary file. + pub fn store_json(&self, name: &str, data: &serde_json::Value) -> io::Result { + let json_str = serde_json::to_string(data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + self.store_string(name, &json_str) + } + + /// Read bytes from a stored file. + pub fn read_bytes(&self, name: &str) -> io::Result> { + let path = self.dir.path().join(name); + std::fs::read(&path) + } + + /// Read string from a stored file. + pub fn read_string(&self, name: &str) -> io::Result { + let path = self.dir.path().join(name); + std::fs::read_to_string(&path) + } + + /// Read JSON from a stored file. + pub fn read_json(&self, name: &str) -> io::Result { + let content = self.read_string(name)?; + serde_json::from_str(&content).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } + + /// Check if a file exists in storage. + pub fn exists(&self, name: &str) -> bool { + self.dir.path().join(name).exists() + } + + /// Remove a file from storage. + pub fn remove(&self, name: &str) -> io::Result<()> { + let path = self.dir.path().join(name); + if path.exists() { + std::fs::remove_file(&path)?; + } + Ok(()) + } + + /// List all files in storage. + pub fn list(&self) -> io::Result> { + let mut files = Vec::new(); + for entry in std::fs::read_dir(self.dir.path())? { + let entry = entry?; + if let Some(name) = entry.file_name().to_str() { + files.push(name.to_string()); + } + } + Ok(files) + } + + /// Get total size of all stored files in bytes. + pub fn total_size(&self) -> io::Result { + let mut total = 0; + for entry in std::fs::read_dir(self.dir.path())? { + let entry = entry?; + total += entry.metadata()?.len(); + } + Ok(total) + } + + /// Create a new temporary file for streaming writes. + pub fn create_temp_file(&self) -> io::Result { + let file = NamedTempFile::new_in(self.dir.path())?; + Ok(TempFile { file }) + } + + /// Persist the storage directory (prevents automatic cleanup). + pub fn persist(self) -> PathBuf { + self.dir.keep() + } +} + +impl Default for TempStorage { + fn default() -> Self { + match Self::new() { + Ok(s) => s, + Err(e) => { + log::error!("TempStorage::new() failed: {e}, falling back to std::env::temp_dir()"); + let fallback = + std::env::temp_dir().join(format!("spider_tmp_{}", std::process::id())); + let _ = std::fs::create_dir_all(&fallback); + // Second attempt with explicit path; if this also fails we + // have bigger OS-level problems and a panic is acceptable. + Self::new_in(&fallback) + .expect("TempStorage fallback in std::env::temp_dir() also failed") + } + } + } +} + +/// A temporary file for streaming operations. +#[derive(Debug)] +pub struct TempFile { + file: NamedTempFile, +} + +impl TempFile { + /// Create a new temporary file. + pub fn new() -> io::Result { + let file = NamedTempFile::new()?; + Ok(Self { file }) + } + + /// Get the path to the temporary file. + pub fn path(&self) -> &Path { + self.file.path() + } + + /// Write bytes to the file. + pub fn write_all(&mut self, data: &[u8]) -> io::Result<()> { + self.file.write_all(data) + } + + /// Write a string to the file. + pub fn write_str(&mut self, data: &str) -> io::Result<()> { + self.write_all(data.as_bytes()) + } + + /// Flush the file buffer. + pub fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } + + /// Read all contents from the file. + pub fn read_all(&mut self) -> io::Result> { + use std::io::Seek; + self.file.seek(std::io::SeekFrom::Start(0))?; + let mut contents = Vec::new(); + self.file.read_to_end(&mut contents)?; + Ok(contents) + } + + /// Read contents as string. + pub fn read_string(&mut self) -> io::Result { + let bytes = self.read_all()?; + String::from_utf8(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } + + /// Persist the file (returns the path and prevents automatic cleanup). + pub fn persist(self) -> io::Result { + let (_, path) = self.file.keep()?; + Ok(path) + } +} + +impl Write for TempFile { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.file.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } +} + +impl Read for TempFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.file.read(buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_temp_storage_basic() { + let storage = TempStorage::new().unwrap(); + + // Store and read bytes + let data = b"hello world"; + storage.store_bytes("test.txt", data).unwrap(); + let read = storage.read_bytes("test.txt").unwrap(); + assert_eq!(read, data); + + // Check exists + assert!(storage.exists("test.txt")); + assert!(!storage.exists("nonexistent.txt")); + + // List files + let files = storage.list().unwrap(); + assert!(files.contains(&"test.txt".to_string())); + } + + #[test] + fn test_temp_storage_json() { + let storage = TempStorage::new().unwrap(); + + let json = serde_json::json!({ + "name": "test", + "value": 42 + }); + + storage.store_json("data.json", &json).unwrap(); + let read = storage.read_json("data.json").unwrap(); + assert_eq!(read, json); + } + + #[test] + fn test_temp_file() { + let mut file = TempFile::new().unwrap(); + + file.write_str("hello").unwrap(); + file.flush().unwrap(); + + let content = file.read_string().unwrap(); + assert_eq!(content, "hello"); + } +} diff --git a/spider_agent/src/tools.rs b/spider_agent/src/tools.rs new file mode 100644 index 000000000..1792424b4 --- /dev/null +++ b/spider_agent/src/tools.rs @@ -0,0 +1,884 @@ +//! Custom tool support for external API calls. + +use dashmap::DashMap; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::time::Duration; + +use crate::error::{AgentError, AgentResult}; + +const DEFAULT_SPIDER_CLOUD_API_URL: &str = "https://api.spider.cloud"; +const DEFAULT_SPIDER_CLOUD_AUTH_HEADER: &str = "Authorization"; +const DEFAULT_TOOL_PREFIX: &str = "spider_cloud"; + +fn strip_bearer_prefix(value: &str) -> &str { + let trimmed = value.trim(); + if trimmed.len() >= 7 && trimmed[..7].eq_ignore_ascii_case("bearer ") { + trimmed[7..].trim_start() + } else { + trimmed + } +} + +/// HTTP method for API calls. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum HttpMethod { + /// GET request. + Get, + /// POST request. + Post, + /// PUT request. + Put, + /// PATCH request. + Patch, + /// DELETE request. + Delete, +} + +impl HttpMethod { + fn as_reqwest_method(&self) -> reqwest::Method { + match self { + HttpMethod::Get => reqwest::Method::GET, + HttpMethod::Post => reqwest::Method::POST, + HttpMethod::Put => reqwest::Method::PUT, + HttpMethod::Patch => reqwest::Method::PATCH, + HttpMethod::Delete => reqwest::Method::DELETE, + } + } +} + +/// Authentication configuration for custom tools. +#[derive(Debug, Clone)] +pub enum AuthConfig { + /// No authentication. + None, + /// Bearer token authentication. + Bearer(String), + /// API key in header. + ApiKey { + /// Header name for the API key. + header: String, + /// API key value. + key: String, + }, + /// Basic authentication. + Basic { + /// Username. + username: String, + /// Password. + password: String, + }, + /// Custom header authentication. + CustomHeader { + /// Header name. + name: String, + /// Header value. + value: String, + }, +} + +/// Configuration for Spider Cloud tool registration. +/// +/// By default this registers core routes: +/// - `/crawl` +/// - `/scrape` +/// - `/search` +/// - `/links` +/// - `/transform` +/// - `/unblocker` +/// +/// AI routes are disabled by default and must be explicitly enabled with +/// `with_enable_ai_routes(true)` because they require a Spider Cloud AI plan. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct SpiderCloudToolConfig { + /// Spider Cloud API key. + pub api_key: String, + /// Spider Cloud API base URL. + pub api_url: String, + /// Prefix used for registered tool names. + /// + /// Default: `spider_cloud`, resulting in names like `spider_cloud_scrape`. + /// Set to empty string for unprefixed names (`scrape`, `search`, etc.). + pub tool_name_prefix: String, + /// Header used for API key auth. Defaults to `Authorization`. + pub auth_header: String, + /// Whether to use `Bearer ` formatting for the Authorization header. + /// + /// Spider Cloud expects raw `Authorization: ` by default, so this is + /// `false` unless explicitly enabled. + pub use_bearer_auth: bool, + /// Request timeout in seconds for each tool call. + pub timeout_secs: u64, + /// Register `/crawl`. + pub include_crawl: bool, + /// Register `/scrape`. + pub include_scrape: bool, + /// Register `/search`. + pub include_search: bool, + /// Register `/links`. + pub include_links: bool, + /// Register `/transform`. + pub include_transform: bool, + /// Register `/unblocker`. + pub include_unblocker: bool, + /// Register `/ai/*` routes. + /// + /// These routes require a paid Spider Cloud AI subscription: + /// https://spider.cloud/ai/pricing + pub enable_ai_routes: bool, +} + +impl Default for SpiderCloudToolConfig { + fn default() -> Self { + Self { + api_key: String::new(), + api_url: DEFAULT_SPIDER_CLOUD_API_URL.to_string(), + tool_name_prefix: DEFAULT_TOOL_PREFIX.to_string(), + auth_header: DEFAULT_SPIDER_CLOUD_AUTH_HEADER.to_string(), + use_bearer_auth: false, + timeout_secs: 60, + include_crawl: true, + include_scrape: true, + include_search: true, + include_links: true, + include_transform: true, + include_unblocker: true, + enable_ai_routes: false, + } + } +} + +impl SpiderCloudToolConfig { + /// Create a Spider Cloud config with core routes enabled. + pub fn new(api_key: impl Into) -> Self { + Self { + api_key: api_key.into(), + ..Self::default() + } + } + + /// Set Spider Cloud API base URL. + pub fn with_api_url(mut self, api_url: impl Into) -> Self { + self.api_url = api_url.into(); + self + } + + /// Set the prefix for generated tool names. + /// + /// Example: + /// - prefix `spider_cloud` -> `spider_cloud_search` + /// - prefix `web_api` -> `web_api_search` + /// - empty prefix -> `search` + pub fn with_tool_name_prefix(mut self, prefix: impl Into) -> Self { + self.tool_name_prefix = prefix.into(); + self + } + + /// Set auth header name. Use non-default header names for custom gateways. + pub fn with_auth_header(mut self, auth_header: impl Into) -> Self { + self.auth_header = auth_header.into(); + self + } + + /// Enable/disable Bearer formatting for Authorization auth. + /// + /// When `true`, sends `Authorization: Bearer `. + /// When `false` (default), sends `Authorization: `. + pub fn with_bearer_auth(mut self, enabled: bool) -> Self { + self.use_bearer_auth = enabled; + self + } + + /// Set timeout in seconds for each registered tool. + pub fn with_timeout_secs(mut self, timeout_secs: u64) -> Self { + self.timeout_secs = timeout_secs.max(1); + self + } + + /// Enable or disable `/unblocker` route registration. + pub fn with_unblocker(mut self, enabled: bool) -> Self { + self.include_unblocker = enabled; + self + } + + /// Enable or disable `/transform` route registration. + pub fn with_transform(mut self, enabled: bool) -> Self { + self.include_transform = enabled; + self + } + + /// Enable or disable AI route registration. + /// + /// AI routes require a paid Spider Cloud AI plan: + /// https://spider.cloud/ai/pricing + pub fn with_enable_ai_routes(mut self, enabled: bool) -> Self { + self.enable_ai_routes = enabled; + self + } + + fn endpoint(&self, route: &str) -> String { + format!( + "{}/{}", + self.api_url.trim_end_matches('/'), + route.trim_start_matches('/') + ) + } + + fn tool_name(&self, suffix: &str) -> String { + let prefix = self.tool_name_prefix.trim().trim_end_matches('_'); + if prefix.is_empty() { + suffix.to_string() + } else { + format!("{}_{}", prefix, suffix) + } + } + + fn auth_tool(&self, tool: CustomTool) -> CustomTool { + if self + .auth_header + .eq_ignore_ascii_case(DEFAULT_SPIDER_CLOUD_AUTH_HEADER) + { + // Accept env inputs like `SPIDER_CLOUD_API_KEY=...` and + // `SPIDER_CLOUD_API_KEY=Bearer ...` without double-prefixing. + let token = strip_bearer_prefix(&self.api_key).to_string(); + if self.use_bearer_auth { + tool.with_bearer_auth(token) + } else { + tool.with_api_key(self.auth_header.clone(), token) + } + } else { + tool.with_api_key(self.auth_header.clone(), self.api_key.trim().to_string()) + } + } + + fn build_tool(&self, name: &str, route: &str, description: &str) -> CustomTool { + let tool = CustomTool::new(name, self.endpoint(route)) + .with_description(description) + .with_method(HttpMethod::Post) + .with_content_type("application/json") + .with_timeout(Duration::from_secs(self.timeout_secs)) + .with_header( + "User-Agent", + format!("spider_agent/{}", env!("CARGO_PKG_VERSION")), + ); + self.auth_tool(tool) + } + + /// Build Spider Cloud tools from this configuration. + pub fn to_custom_tools(&self) -> Vec { + let mut tools = Vec::new(); + + if self.include_crawl { + tools.push(self.build_tool( + &self.tool_name("crawl"), + "crawl", + "Spider Cloud /crawl endpoint for crawling and extraction.", + )); + } + if self.include_scrape { + tools.push(self.build_tool( + &self.tool_name("scrape"), + "scrape", + "Spider Cloud /scrape endpoint for page scraping and extraction.", + )); + } + if self.include_search { + tools.push(self.build_tool( + &self.tool_name("search"), + "search", + "Spider Cloud /search endpoint for web search plus page retrieval.", + )); + } + if self.include_links { + tools.push(self.build_tool( + &self.tool_name("links"), + "links", + "Spider Cloud /links endpoint for link extraction only.", + )); + } + if self.include_transform { + tools.push(self.build_tool( + &self.tool_name("transform"), + "transform", + "Spider Cloud /transform endpoint for structured content transformation.", + )); + } + if self.include_unblocker { + tools.push(self.build_tool( + &self.tool_name("unblocker"), + "unblocker", + "Spider Cloud /unblocker endpoint for anti-bot bypass and hard-to-reach pages.", + )); + } + + if self.enable_ai_routes { + tools.push(self.build_tool( + &self.tool_name("ai_crawl"), + "ai/crawl", + "Spider Cloud /ai/crawl endpoint for AI-guided crawling (AI subscription required).", + )); + tools.push(self.build_tool( + &self.tool_name("ai_scrape"), + "ai/scrape", + "Spider Cloud /ai/scrape endpoint for AI-guided scraping (AI subscription required).", + )); + tools.push(self.build_tool( + &self.tool_name("ai_search"), + "ai/search", + "Spider Cloud /ai/search endpoint for AI-enhanced search (AI subscription required).", + )); + tools.push(self.build_tool( + &self.tool_name("ai_browser"), + "ai/browser", + "Spider Cloud /ai/browser endpoint for AI browser automation (AI subscription required).", + )); + tools.push(self.build_tool( + &self.tool_name("ai_links"), + "ai/links", + "Spider Cloud /ai/links endpoint for AI link extraction (AI subscription required).", + )); + } + + tools + } +} + +/// Configuration for a custom tool (external API call). +#[derive(Debug, Clone)] +pub struct CustomTool { + /// Unique name for this tool. + pub name: String, + /// Description of what this tool does. + pub description: String, + /// Base URL for the API. + pub base_url: String, + /// Default HTTP method. + pub method: HttpMethod, + /// Authentication configuration. + pub auth: AuthConfig, + /// Additional headers. + pub headers: Vec<(String, String)>, + /// Request timeout. + pub timeout: Duration, + /// Content type for requests. + pub content_type: Option, +} + +impl CustomTool { + /// Create a new custom tool with GET method. + pub fn new(name: impl Into, base_url: impl Into) -> Self { + Self { + name: name.into(), + description: String::new(), + base_url: base_url.into(), + method: HttpMethod::Get, + auth: AuthConfig::None, + headers: Vec::new(), + timeout: Duration::from_secs(30), + content_type: None, + } + } + + /// Set description. + pub fn with_description(mut self, desc: impl Into) -> Self { + self.description = desc.into(); + self + } + + /// Set HTTP method. + pub fn with_method(mut self, method: HttpMethod) -> Self { + self.method = method; + self + } + + /// Set bearer token authentication. + pub fn with_bearer_auth(mut self, token: impl Into) -> Self { + self.auth = AuthConfig::Bearer(token.into()); + self + } + + /// Set API key authentication. + pub fn with_api_key(mut self, header: impl Into, key: impl Into) -> Self { + self.auth = AuthConfig::ApiKey { + header: header.into(), + key: key.into(), + }; + self + } + + /// Set basic authentication. + pub fn with_basic_auth( + mut self, + username: impl Into, + password: impl Into, + ) -> Self { + self.auth = AuthConfig::Basic { + username: username.into(), + password: password.into(), + }; + self + } + + /// Set custom header authentication. + pub fn with_custom_auth(mut self, name: impl Into, value: impl Into) -> Self { + self.auth = AuthConfig::CustomHeader { + name: name.into(), + value: value.into(), + }; + self + } + + /// Add a custom header. + pub fn with_header(mut self, name: impl Into, value: impl Into) -> Self { + self.headers.push((name.into(), value.into())); + self + } + + /// Set request timeout. + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Set content type. + pub fn with_content_type(mut self, content_type: impl Into) -> Self { + self.content_type = Some(content_type.into()); + self + } + + /// Build the headers for a request. + fn build_headers(&self) -> AgentResult { + let mut headers = HeaderMap::new(); + + // Add authentication + match &self.auth { + AuthConfig::None => {} + AuthConfig::Bearer(token) => { + headers.insert( + reqwest::header::AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| AgentError::Tool(format!("Invalid bearer token: {}", e)))?, + ); + } + AuthConfig::ApiKey { header, key } => { + let header_name = HeaderName::try_from(header.as_str()) + .map_err(|e| AgentError::Tool(format!("Invalid header name: {}", e)))?; + let header_value = HeaderValue::from_str(key) + .map_err(|e| AgentError::Tool(format!("Invalid API key: {}", e)))?; + headers.insert(header_name, header_value); + } + AuthConfig::Basic { username, password } => { + let credentials = base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + format!("{}:{}", username, password), + ); + headers.insert( + reqwest::header::AUTHORIZATION, + HeaderValue::from_str(&format!("Basic {}", credentials)) + .map_err(|e| AgentError::Tool(format!("Invalid basic auth: {}", e)))?, + ); + } + AuthConfig::CustomHeader { name, value } => { + let header_name = HeaderName::try_from(name.as_str()) + .map_err(|e| AgentError::Tool(format!("Invalid header name: {}", e)))?; + let header_value = HeaderValue::from_str(value) + .map_err(|e| AgentError::Tool(format!("Invalid header value: {}", e)))?; + headers.insert(header_name, header_value); + } + } + + // Add content type if specified + if let Some(ref ct) = self.content_type { + headers.insert( + reqwest::header::CONTENT_TYPE, + HeaderValue::from_str(ct) + .map_err(|e| AgentError::Tool(format!("Invalid content type: {}", e)))?, + ); + } + + // Add custom headers + for (name, value) in &self.headers { + let header_name = HeaderName::try_from(name.as_str()) + .map_err(|e| AgentError::Tool(format!("Invalid header name '{}': {}", name, e)))?; + let header_value = HeaderValue::from_str(value).map_err(|e| { + AgentError::Tool(format!("Invalid header value for '{}': {}", name, e)) + })?; + headers.insert(header_name, header_value); + } + + Ok(headers) + } +} + +/// Result from executing a custom tool. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CustomToolResult { + /// The tool name that was executed. + pub tool_name: String, + /// HTTP status code. + pub status: u16, + /// Response body. + pub body: String, + /// Response headers. + pub headers: Vec<(String, String)>, + /// Whether the request was successful (2xx status). + pub success: bool, +} + +/// Registry for custom tools. +#[derive(Debug, Default)] +pub struct CustomToolRegistry { + tools: DashMap>, +} + +impl CustomToolRegistry { + /// Create a new empty registry. + pub fn new() -> Self { + Self { + tools: DashMap::new(), + } + } + + /// Register a custom tool. + pub fn register(&self, tool: CustomTool) { + self.tools.insert(tool.name.clone(), Arc::new(tool)); + } + + /// Get a tool by name. + pub fn get(&self, name: &str) -> Option> { + self.tools.get(name).map(|r| r.clone()) + } + + /// Remove a tool. + pub fn remove(&self, name: &str) -> Option> { + self.tools.remove(name).map(|(_, v)| v) + } + + /// List all registered tools. + pub fn list(&self) -> Vec { + self.tools.iter().map(|e| e.key().clone()).collect() + } + + /// Check if a tool is registered. + pub fn contains(&self, name: &str) -> bool { + self.tools.contains_key(name) + } + + /// Clear all tools. + pub fn clear(&self) { + self.tools.clear(); + } + + /// Register Spider Cloud tools from a shared config. + /// + /// Returns the number of tools registered. + pub fn register_spider_cloud(&self, config: &SpiderCloudToolConfig) -> usize { + let tools = config.to_custom_tools(); + let count = tools.len(); + for tool in tools { + self.register(tool); + } + count + } + + /// Execute a custom tool. + pub async fn execute( + &self, + name: &str, + client: &reqwest::Client, + path: Option<&str>, + query: Option<&[(&str, &str)]>, + body: Option<&str>, + ) -> AgentResult { + let tool = self + .get(name) + .ok_or_else(|| AgentError::Tool(format!("Custom tool '{}' not found", name)))?; + + // Build URL + let mut url = tool.base_url.clone(); + if let Some(p) = path { + if !url.ends_with('/') && !p.starts_with('/') { + url.push('/'); + } + url.push_str(p); + } + + // Build request + let mut request = client + .request(tool.method.as_reqwest_method(), &url) + .timeout(tool.timeout) + .headers(tool.build_headers()?); + + // Add query parameters + if let Some(q) = query { + request = request.query(q); + } + + // Add body + if let Some(b) = body { + request = request.body(b.to_string()); + } + + // Execute + let response = request.send().await?; + + let status = response.status().as_u16(); + let success = response.status().is_success(); + + let headers: Vec<(String, String)> = response + .headers() + .iter() + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + + let body = response.text().await?; + + Ok(CustomToolResult { + tool_name: name.to_string(), + status, + body, + headers, + success, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_custom_tool_builder() { + let tool = CustomTool::new("my_api", "https://api.example.com") + .with_description("My custom API") + .with_method(HttpMethod::Post) + .with_bearer_auth("secret_token") + .with_header("X-Custom", "value") + .with_timeout(Duration::from_secs(60)) + .with_content_type("application/json"); + + assert_eq!(tool.name, "my_api"); + assert_eq!(tool.base_url, "https://api.example.com"); + assert_eq!(tool.description, "My custom API"); + assert_eq!(tool.method, HttpMethod::Post); + assert_eq!(tool.timeout, Duration::from_secs(60)); + assert_eq!(tool.content_type, Some("application/json".to_string())); + assert_eq!(tool.headers.len(), 1); + assert!(matches!(tool.auth, AuthConfig::Bearer(_))); + } + + #[test] + fn test_custom_tool_registry() { + let registry = CustomToolRegistry::new(); + + // Register tools + let tool1 = CustomTool::new("api_1", "https://api1.example.com"); + let tool2 = CustomTool::new("api_2", "https://api2.example.com"); + + registry.register(tool1); + registry.register(tool2); + + // Check registration + assert!(registry.contains("api_1")); + assert!(registry.contains("api_2")); + assert!(!registry.contains("api_3")); + + // List tools + let tools = registry.list(); + assert_eq!(tools.len(), 2); + assert!(tools.contains(&"api_1".to_string())); + assert!(tools.contains(&"api_2".to_string())); + + // Get tool + let tool = registry.get("api_1"); + assert!(tool.is_some()); + assert_eq!(tool.unwrap().base_url, "https://api1.example.com"); + + // Remove tool + let removed = registry.remove("api_1"); + assert!(removed.is_some()); + assert!(!registry.contains("api_1")); + + // Clear + registry.clear(); + assert!(registry.list().is_empty()); + } + + #[test] + fn test_auth_config_variants() { + let tool = + CustomTool::new("test", "https://example.com").with_api_key("X-API-Key", "my_key"); + assert!(matches!(tool.auth, AuthConfig::ApiKey { .. })); + + let tool = CustomTool::new("test", "https://example.com").with_basic_auth("user", "pass"); + assert!(matches!(tool.auth, AuthConfig::Basic { .. })); + + let tool = CustomTool::new("test", "https://example.com") + .with_custom_auth("X-Custom-Auth", "token123"); + assert!(matches!(tool.auth, AuthConfig::CustomHeader { .. })); + } + + #[test] + fn test_http_method_conversion() { + assert_eq!(HttpMethod::Get.as_reqwest_method(), reqwest::Method::GET); + assert_eq!(HttpMethod::Post.as_reqwest_method(), reqwest::Method::POST); + assert_eq!(HttpMethod::Put.as_reqwest_method(), reqwest::Method::PUT); + assert_eq!( + HttpMethod::Patch.as_reqwest_method(), + reqwest::Method::PATCH + ); + assert_eq!( + HttpMethod::Delete.as_reqwest_method(), + reqwest::Method::DELETE + ); + } + + #[test] + fn test_custom_tool_result() { + let result = CustomToolResult { + tool_name: "my_api".to_string(), + status: 200, + body: r#"{"success": true}"#.to_string(), + headers: vec![("content-type".to_string(), "application/json".to_string())], + success: true, + }; + + assert_eq!(result.tool_name, "my_api"); + assert_eq!(result.status, 200); + assert!(result.success); + } + + #[test] + fn test_spider_cloud_tools_default_routes_only() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud"); + let tools = cfg.to_custom_tools(); + let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect(); + + assert_eq!(tools.len(), 6); + assert!(names.contains(&"spider_cloud_crawl")); + assert!(names.contains(&"spider_cloud_scrape")); + assert!(names.contains(&"spider_cloud_search")); + assert!(names.contains(&"spider_cloud_links")); + assert!(names.contains(&"spider_cloud_transform")); + assert!(names.contains(&"spider_cloud_unblocker")); + + assert!(!names.contains(&"spider_cloud_ai_crawl")); + assert!(!names.contains(&"spider_cloud_ai_scrape")); + assert!(!names.contains(&"spider_cloud_ai_search")); + assert!(!names.contains(&"spider_cloud_ai_browser")); + assert!(!names.contains(&"spider_cloud_ai_links")); + + // Default auth should be raw Authorization header (not Bearer). + let crawl = tools + .iter() + .find(|t| t.name == "spider_cloud_crawl") + .expect("crawl tool"); + assert!(matches!( + crawl.auth, + AuthConfig::ApiKey { + ref header, + ref key + } if header == "Authorization" && key == "sk_spider_cloud" + )); + } + + #[test] + fn test_spider_cloud_tools_with_ai_subscription_enabled() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud").with_enable_ai_routes(true); + let tools = cfg.to_custom_tools(); + let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect(); + + assert_eq!(tools.len(), 11); + assert!(names.contains(&"spider_cloud_ai_crawl")); + assert!(names.contains(&"spider_cloud_ai_scrape")); + assert!(names.contains(&"spider_cloud_ai_search")); + assert!(names.contains(&"spider_cloud_ai_browser")); + assert!(names.contains(&"spider_cloud_ai_links")); + } + + #[test] + fn test_spider_cloud_registry_registration() { + let registry = CustomToolRegistry::new(); + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud") + .with_unblocker(true) + .with_transform(true) + .with_enable_ai_routes(false); + let count = registry.register_spider_cloud(&cfg); + + assert_eq!(count, 6); + assert!(registry.contains("spider_cloud_crawl")); + assert!(registry.contains("spider_cloud_transform")); + assert!(registry.contains("spider_cloud_unblocker")); + assert!(!registry.contains("spider_cloud_ai_scrape")); + } + + #[test] + fn test_spider_cloud_bearer_auth_opt_in() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud").with_bearer_auth(true); + let tools = cfg.to_custom_tools(); + let crawl = tools + .iter() + .find(|t| t.name == "spider_cloud_crawl") + .expect("crawl tool"); + assert!(matches!(crawl.auth, AuthConfig::Bearer(ref t) if t == "sk_spider_cloud")); + } + + #[test] + fn test_spider_cloud_strips_bearer_prefix_in_default_mode() { + let cfg = SpiderCloudToolConfig::new("Bearer sk_spider_cloud"); + let tools = cfg.to_custom_tools(); + let crawl = tools + .iter() + .find(|t| t.name == "spider_cloud_crawl") + .expect("crawl tool"); + assert!(matches!( + crawl.auth, + AuthConfig::ApiKey { + ref header, + ref key + } if header == "Authorization" && key == "sk_spider_cloud" + )); + } + + #[test] + fn test_spider_cloud_bearer_opt_in_avoids_double_prefix() { + let cfg = SpiderCloudToolConfig::new("Bearer sk_spider_cloud").with_bearer_auth(true); + let tools = cfg.to_custom_tools(); + let crawl = tools + .iter() + .find(|t| t.name == "spider_cloud_crawl") + .expect("crawl tool"); + assert!(matches!(crawl.auth, AuthConfig::Bearer(ref t) if t == "sk_spider_cloud")); + } + + #[test] + fn test_spider_cloud_custom_prefix_and_api_url() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud") + .with_api_url("https://custom.provider.local/v1") + .with_tool_name_prefix("web_api") + .with_enable_ai_routes(false); + let tools = cfg.to_custom_tools(); + + let transform = tools + .iter() + .find(|t| t.name == "web_api_transform") + .expect("transform tool with custom prefix"); + assert_eq!( + transform.base_url, + "https://custom.provider.local/v1/transform" + ); + } + + #[test] + fn test_spider_cloud_empty_prefix_uses_plain_names() { + let cfg = SpiderCloudToolConfig::new("sk_spider_cloud").with_tool_name_prefix(""); + let tools = cfg.to_custom_tools(); + let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect(); + + assert!(names.contains(&"crawl")); + assert!(names.contains(&"search")); + assert!(names.contains(&"transform")); + } +} diff --git a/spider_agent/src/webdriver.rs b/spider_agent/src/webdriver.rs new file mode 100644 index 000000000..2e8e2a105 --- /dev/null +++ b/spider_agent/src/webdriver.rs @@ -0,0 +1,246 @@ +//! WebDriver automation module for spider_agent. +//! +//! Provides WebDriver session management with support for: +//! - Multiple browser backends (Chrome, Firefox, Edge) +//! - Page navigation and interaction +//! - Screenshot capture +//! - JavaScript execution + +use std::sync::Arc; +use thirtyfour::error::WebDriverError; +use thirtyfour::prelude::*; + +/// Result type for WebDriver operations. +pub type WebDriverResult = Result; + +/// WebDriver context for browser automation. +/// +/// Wraps a thirtyfour WebDriver session with utilities for agent operations. +#[derive(Clone)] +pub struct WebDriverContext { + /// The WebDriver session. + driver: Arc, +} + +impl WebDriverContext { + /// Create a new WebDriver context from an existing WebDriver. + pub fn new(driver: Arc) -> Self { + Self { driver } + } + + /// Create from a WebDriver (wraps in Arc). + pub fn from_driver(driver: WebDriver) -> Self { + Self { + driver: Arc::new(driver), + } + } + + /// Get the WebDriver instance. + pub fn driver(&self) -> &Arc { + &self.driver + } + + /// Navigate to a URL. + pub async fn navigate(&self, url: &str) -> WebDriverResult<()> { + self.driver.goto(url).await + } + + /// Get the current URL. + pub async fn url(&self) -> WebDriverResult { + Ok(self.driver.current_url().await?.to_string()) + } + + /// Get the page HTML content. + pub async fn html(&self) -> WebDriverResult { + self.driver.source().await + } + + /// Get the page title. + pub async fn title(&self) -> WebDriverResult { + self.driver.title().await + } + + /// Take a screenshot and return PNG bytes. + pub async fn screenshot(&self) -> WebDriverResult> { + self.driver.screenshot_as_png().await + } + + /// Find an element by CSS selector. + pub async fn find_element(&self, selector: &str) -> WebDriverResult { + self.driver.find(By::Css(selector)).await + } + + /// Find all elements matching a CSS selector. + pub async fn find_elements(&self, selector: &str) -> WebDriverResult> { + self.driver.find_all(By::Css(selector)).await + } + + /// Click an element by CSS selector. + pub async fn click(&self, selector: &str) -> WebDriverResult<()> { + let element = self.find_element(selector).await?; + element.click().await + } + + /// Type text into an element. + pub async fn type_text(&self, selector: &str, text: &str) -> WebDriverResult<()> { + let element = self.find_element(selector).await?; + element.send_keys(text).await + } + + /// Clear an element's value. + pub async fn clear(&self, selector: &str) -> WebDriverResult<()> { + let element = self.find_element(selector).await?; + element.clear().await + } + + /// Get an element's text content. + pub async fn get_text(&self, selector: &str) -> WebDriverResult { + let element = self.find_element(selector).await?; + element.text().await + } + + /// Get an element's attribute value. + pub async fn get_attribute( + &self, + selector: &str, + attr: &str, + ) -> WebDriverResult> { + let element = self.find_element(selector).await?; + element.attr(attr).await + } + + /// Execute JavaScript and return the result as JSON. + pub async fn execute( + &self, + script: &str, + ) -> WebDriverResult { + let ret = self.driver.execute(script, vec![]).await?; + ret.convert() + } + + /// Execute JavaScript without returning a value. + pub async fn execute_script(&self, script: &str) -> WebDriverResult<()> { + self.driver.execute(script, vec![]).await?; + Ok(()) + } + + /// Wait for an element to be present. + pub async fn wait_for(&self, selector: &str, timeout_secs: u64) -> WebDriverResult { + let elem = self + .driver + .query(By::Css(selector)) + .wait( + std::time::Duration::from_secs(timeout_secs), + std::time::Duration::from_millis(100), + ) + .first() + .await?; + Ok(elem) + } + + /// Wait for element to be clickable. + pub async fn wait_for_clickable( + &self, + selector: &str, + timeout_secs: u64, + ) -> WebDriverResult { + let elem = self + .driver + .query(By::Css(selector)) + .wait( + std::time::Duration::from_secs(timeout_secs), + std::time::Duration::from_millis(100), + ) + .and_clickable() + .first() + .await?; + Ok(elem) + } + + /// Open a new tab/window. + pub async fn new_tab(&self) -> WebDriverResult { + self.driver.new_tab().await + } + + /// Open a new window. + pub async fn new_window(&self) -> WebDriverResult { + self.driver.new_window().await + } + + /// Switch to a window by handle. + pub async fn switch_to_window(&self, handle: &WindowHandle) -> WebDriverResult<()> { + self.driver.switch_to_window(handle.clone()).await + } + + /// Get all window handles. + pub async fn windows(&self) -> WebDriverResult> { + self.driver.windows().await + } + + /// Get current window handle. + pub async fn current_window(&self) -> WebDriverResult { + self.driver.window().await + } + + /// Close the current window. + pub async fn close_window(&self) -> WebDriverResult<()> { + self.driver.close_window().await + } + + /// Refresh the page. + pub async fn refresh(&self) -> WebDriverResult<()> { + self.driver.refresh().await + } + + /// Go back in history. + pub async fn back(&self) -> WebDriverResult<()> { + self.driver.back().await + } + + /// Go forward in history. + pub async fn forward(&self) -> WebDriverResult<()> { + self.driver.forward().await + } + + /// Set window size. + pub async fn set_window_size(&self, width: u32, height: u32) -> WebDriverResult<()> { + self.driver.set_window_rect(0, 0, width, height).await?; + Ok(()) + } + + /// Maximize window. + pub async fn maximize_window(&self) -> WebDriverResult<()> { + self.driver.maximize_window().await + } + + /// Minimize window. + pub async fn minimize_window(&self) -> WebDriverResult<()> { + self.driver.minimize_window().await + } + + /// Get all cookies. + pub async fn cookies(&self) -> WebDriverResult> { + self.driver.get_all_cookies().await + } + + /// Delete a cookie by name. + pub async fn delete_cookie(&self, name: &str) -> WebDriverResult<()> { + self.driver.delete_cookie(name).await + } + + /// Delete all cookies. + pub async fn delete_all_cookies(&self) -> WebDriverResult<()> { + self.driver.delete_all_cookies().await + } +} + +impl std::fmt::Debug for WebDriverContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WebDriverContext") + .field("driver", &"WebDriver { ... }") + .finish() + } +} + +// Re-export useful types +pub use thirtyfour::{By, Cookie, WebDriver, WebElement, WindowHandle}; diff --git a/spider_agent/tests/action_feedback_integration.rs b/spider_agent/tests/action_feedback_integration.rs new file mode 100644 index 000000000..cd3b7019d --- /dev/null +++ b/spider_agent/tests/action_feedback_integration.rs @@ -0,0 +1,211 @@ +//! Integration tests for action feedback, partial result preservation, +//! and Evaluate JS error capture improvements. +//! +//! These tests verify the improvements at the integration boundary — they +//! exercise the actual types and formatting logic that the agent loop uses +//! without requiring a running browser. +//! +//! For live LLM integration, set `RUN_LIVE_TESTS=1` and `OPENROUTER_API_KEY`. +//! +//! Run with: +//! ```sh +//! cargo test -p spider_agent --features chrome --test action_feedback_integration +//! ``` + +#[cfg(feature = "chrome")] +mod feedback_tests { + use spider_agent::{AutomationResult, AutomationUsage, EngineError}; + + // ==================================================================== + // Partial result preservation: public API surface + // ==================================================================== + + #[test] + fn partial_result_round_trip_json() { + let result = AutomationResult { + label: "scraping".to_string(), + steps_executed: 7, + success: false, + error: Some("LLM inference failed on round 3: Http timeout".to_string()), + usage: AutomationUsage { + prompt_tokens: 2500, + completion_tokens: 400, + total_tokens: 2900, + llm_calls: 2, + ..Default::default() + }, + extracted: Some(serde_json::json!({ + "products": [ + {"name": "Widget A", "price": 19.99}, + {"name": "Widget B", "price": 29.99}, + ] + })), + screenshot: None, + spawn_pages: vec!["https://example.com/page2".to_string()], + relevant: Some(true), + reasoning: Some("Collecting product data".to_string()), + }; + + // Serialize to JSON and back — all fields must survive + let json = serde_json::to_string(&result).unwrap(); + let parsed: AutomationResult = serde_json::from_str(&json).unwrap(); + + assert!(!parsed.success); + assert_eq!(parsed.steps_executed, 7); + assert!(parsed.error.as_ref().unwrap().contains("round 3")); + assert!(parsed.extracted.is_some()); + let products = parsed.extracted.as_ref().unwrap()["products"] + .as_array() + .unwrap(); + assert_eq!(products.len(), 2); + assert_eq!(parsed.usage.llm_calls, 2); + assert_eq!(parsed.spawn_pages.len(), 1); + assert_eq!(parsed.relevant, Some(true)); + assert!(parsed.reasoning.is_some()); + } + + #[test] + fn partial_result_with_usage_preserved() { + let mut usage = AutomationUsage::default(); + usage.prompt_tokens = 1000; + usage.completion_tokens = 200; + usage.total_tokens = 1200; + usage.llm_calls = 3; + + let result = AutomationResult::failure("test", "LLM error").with_usage(usage.clone()); + + assert!(!result.success); + assert_eq!(result.usage.prompt_tokens, 1000); + assert_eq!(result.usage.llm_calls, 3); + } + + #[test] + fn engine_error_display() { + let http_err_msg = format!("{}", EngineError::Remote("429 rate limited".to_string())); + assert!(http_err_msg.contains("429 rate limited")); + + let missing = format!( + "{}", + EngineError::MissingField("choices[0].message.content") + ); + assert!(missing.contains("choices[0].message.content")); + + let invalid = format!("{}", EngineError::InvalidField("content not JSON")); + assert!(invalid.contains("content not JSON")); + } + + // ==================================================================== + // Verify AutomationResult builder preserves all fields + // ==================================================================== + + #[test] + fn automation_result_builder_chain() { + let result = AutomationResult::success("extraction", 3) + .with_extracted(serde_json::json!({"key": "value"})) + .with_screenshot("base64png".to_string()) + .with_spawn_pages(vec!["https://a.com".to_string()]); + + assert!(result.success); + assert!(result.error.is_none()); + assert!(result.extracted.is_some()); + assert!(result.screenshot.is_some()); + assert_eq!(result.spawn_pages.len(), 1); + } +} + +#[cfg(feature = "chrome")] +mod live_llm_tests { + use std::env; + + fn run_live_tests() -> bool { + matches!( + env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) + } + + /// Integration test that verifies action feedback is properly formatted + /// and could be injected into an LLM prompt. Uses OpenRouter if available. + #[tokio::test] + async fn live_action_feedback_prompt_injection() { + if !run_live_tests() { + eprintln!("Skipping live LLM test (RUN_LIVE_TESTS not enabled)."); + return; + } + + let api_key = match env::var("OPENROUTER_API_KEY") + .ok() + .filter(|v| !v.trim().is_empty()) + { + Some(k) => k, + None => { + eprintln!("Skipping: OPENROUTER_API_KEY not set."); + return; + } + }; + + let model = env::var("OPENROUTER_MODEL") + .unwrap_or_else(|_| "google/gemini-2.0-flash-001".to_string()); + + // Build a prompt that includes action feedback, similar to what the agent sends + let action_feedback = concat!( + "PREVIOUS ACTION RESULTS:\n", + "- Click → ok\n", + "- Fill → FAILED: selector not found: input#email\n", + "- Evaluate → FAILED: JS error: ReferenceError: foo is not defined\n", + "\n" + ); + let user_prompt = format!( + "{}You are a web automation agent. The previous round's actions had failures as shown above. \ + What CSS selector should you try instead of 'input#email' for an email field? \ + Reply with just the selector, nothing else.", + action_feedback + ); + + // Call OpenRouter API directly + let client = reqwest::Client::new(); + let body = serde_json::json!({ + "model": model, + "messages": [ + {"role": "user", "content": user_prompt} + ], + "max_tokens": 100, + "temperature": 0.0, + }); + + let resp = client + .post("https://openrouter.ai/api/v1/chat/completions") + .header("Authorization", format!("Bearer {}", api_key)) + .json(&body) + .send() + .await; + + match resp { + Ok(r) if r.status().is_success() => { + let json: serde_json::Value = r.json().await.unwrap(); + let content = json["choices"][0]["message"]["content"] + .as_str() + .unwrap_or(""); + // The model should suggest an alternative selector + assert!( + !content.trim().is_empty(), + "LLM should return a non-empty alternative selector" + ); + eprintln!("LLM suggested selector: {}", content.trim()); + } + Ok(r) => { + eprintln!( + "OpenRouter returned non-success status: {} — skipping assertion", + r.status() + ); + } + Err(e) => { + eprintln!("OpenRouter request failed: {} — skipping", e); + } + } + } +} diff --git a/spider_agent/tests/live_env_smoke.rs b/spider_agent/tests/live_env_smoke.rs new file mode 100644 index 000000000..5bb867b2b --- /dev/null +++ b/spider_agent/tests/live_env_smoke.rs @@ -0,0 +1,62 @@ +#[cfg(any(feature = "openai", feature = "search_serper"))] +use std::env; + +#[cfg(any(feature = "openai", feature = "search_serper"))] +fn run_live_tests() -> bool { + matches!( + env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +#[cfg(feature = "openai")] +#[tokio::test] +async fn live_openai_prompt_smoke() -> Result<(), Box> { + if !run_live_tests() { + eprintln!("Skipping live OpenAI smoke test (RUN_LIVE_TESTS not enabled)."); + return Ok(()); + } + + let key = env::var("OPENAI_API_KEY") + .ok() + .filter(|v| !v.trim().is_empty()) + .expect("RUN_LIVE_TESTS is enabled, but OPENAI_API_KEY is missing"); + let model = env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-4o-mini".to_string()); + + let agent = spider_agent::Agent::builder() + .with_openai(key, model) + .build()?; + + let output = agent + .prompt(vec![spider_agent::Message::user("Reply with exactly: ok")]) + .await?; + + assert!(!output.trim().is_empty()); + Ok(()) +} + +#[cfg(feature = "search_serper")] +#[tokio::test] +async fn live_serper_search_smoke() -> Result<(), Box> { + if !run_live_tests() { + eprintln!("Skipping live Serper smoke test (RUN_LIVE_TESTS not enabled)."); + return Ok(()); + } + + let key = env::var("SERPER_API_KEY") + .ok() + .filter(|v| !v.trim().is_empty()) + .expect("RUN_LIVE_TESTS is enabled, but SERPER_API_KEY is missing"); + + let agent = spider_agent::Agent::builder() + .with_search_serper(key) + .build()?; + let results = agent.search("Spider Cloud web crawler").await?; + assert!(!results.results.is_empty()); + + Ok(()) +} diff --git a/spider_agent/tests/live_spider_cloud.rs b/spider_agent/tests/live_spider_cloud.rs new file mode 100644 index 000000000..7929e29e5 --- /dev/null +++ b/spider_agent/tests/live_spider_cloud.rs @@ -0,0 +1,132 @@ +use spider_agent::Agent; +use std::env; + +fn run_live_tests() -> bool { + matches!( + env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +fn summarize_response_usage(value: &serde_json::Value) -> String { + let first = if let Some(arr) = value.as_array() { + arr.first() + } else { + Some(value) + }; + + let Some(first) = first else { + return "empty-response".to_string(); + }; + + let total_cost = first + .get("costs") + .and_then(|v| { + v.get("total_cost_formatted") + .or_else(|| v.get("total_cost")) + }) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + let duration_ms = first + .get("duration_elapsed_ms") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + let status = first + .get("status") + .and_then(|v| v.as_u64()) + .map(|v| v.to_string()) + .unwrap_or_else(|| "n/a".to_string()); + + let has_metadata = first.get("metadata").is_some(); + + format!( + "status={}, duration_ms={}, total_cost={}, metadata_present={}", + status, duration_ms, total_cost, has_metadata + ) +} + +#[tokio::test] +async fn live_spider_cloud_search_and_scrape_tools() -> Result<(), Box> { + if !run_live_tests() { + eprintln!("Skipping Spider Cloud live test (RUN_LIVE_TESTS not enabled)."); + return Ok(()); + } + + let api_key = env::var("SPIDER_CLOUD_API_KEY") + .ok() + .filter(|v| !v.trim().is_empty()) + .expect("RUN_LIVE_TESTS is enabled, but SPIDER_CLOUD_API_KEY is missing"); + + let agent = Agent::builder().with_spider_cloud(api_key).build()?; + + assert!(agent.has_custom_tool("spider_cloud_search")); + assert!(agent.has_custom_tool("spider_cloud_scrape")); + + let search_body = serde_json::json!({ + "search": "books to scrape", + "num": 3, + "fetch_page_content": false + }) + .to_string(); + + let search_result = agent + .execute_custom_tool("spider_cloud_search", None, None, Some(&search_body)) + .await?; + assert!( + search_result.success, + "spider_cloud_search failed with HTTP {} body={}", + search_result.status, + search_result.body.chars().take(300).collect::() + ); + let search_json: serde_json::Value = serde_json::from_str(&search_result.body)?; + eprintln!( + "Spider Cloud search usage: {}", + summarize_response_usage(&search_json) + ); + + let scrape_body = serde_json::json!({ + "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", + "return_format": "markdown", + "limit": 1 + }) + .to_string(); + + let scrape_result = agent + .execute_custom_tool("spider_cloud_scrape", None, None, Some(&scrape_body)) + .await?; + assert!( + scrape_result.success, + "spider_cloud_scrape failed with HTTP {} body={}", + scrape_result.status, + scrape_result.body.chars().take(300).collect::() + ); + let scrape_json: serde_json::Value = serde_json::from_str(&scrape_result.body)?; + eprintln!( + "Spider Cloud scrape usage: {}", + summarize_response_usage(&scrape_json) + ); + + let usage = agent.usage(); + eprintln!( + "Agent usage snapshot: search_tool_calls={}, scrape_tool_calls={}, total_custom_tool_calls={}", + usage.get_custom_tool_calls("spider_cloud_search"), + usage.get_custom_tool_calls("spider_cloud_scrape"), + usage.total_custom_tool_calls(), + ); + + assert_eq!(usage.get_custom_tool_calls("spider_cloud_search"), 1); + assert_eq!(usage.get_custom_tool_calls("spider_cloud_scrape"), 1); + assert!( + usage.total_custom_tool_calls() >= 2, + "expected at least 2 custom tool calls" + ); + + Ok(()) +} diff --git a/spider_agent/tests/thinking_integration.rs b/spider_agent/tests/thinking_integration.rs new file mode 100644 index 000000000..31703bbfa --- /dev/null +++ b/spider_agent/tests/thinking_integration.rs @@ -0,0 +1,361 @@ +//! Integration tests for thinking/extended thinking support. +//! +//! Unit tests validate response parsing for both OpenAI and Anthropic formats. +//! Live tests (gated behind RUN_LIVE_TESTS=1) hit real endpoints. + +use serde_json::json; +use spider_agent_types::{ + extract_assistant_content, extract_thinking_content, extract_usage, is_anthropic_endpoint, + parse_tool_calls, +}; + +// ── Unit: Anthropic Messages API response parsing ── + +#[test] +fn anthropic_response_with_thinking() { + let resp = json!({ + "id": "msg_01XFDUDYJgAACzvnptvVoYEL", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "thinking", + "thinking": "The user wants me to extract data from an HTML page. Let me parse the content carefully." + }, + { + "type": "text", + "text": "{\"label\": \"extract_products\", \"done\": true, \"steps\": [], \"extracted\": {\"products\": [{\"name\": \"Widget\", \"price\": 9.99}]}}" + } + ], + "model": "claude-sonnet-4-20250514", + "stop_reason": "end_turn", + "usage": { + "input_tokens": 1523, + "output_tokens": 256, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + } + }); + + // Text content extracted (skipping thinking blocks) + let content = extract_assistant_content(&resp).unwrap(); + assert!(content.contains("extract_products")); + assert!(content.contains("Widget")); + + // Thinking content extracted + let thinking = extract_thinking_content(&resp).unwrap(); + assert!(thinking.contains("parse the content carefully")); + + // Usage mapped correctly + let usage = extract_usage(&resp); + assert_eq!(usage.prompt_tokens, 1523); + assert_eq!(usage.completion_tokens, 256); + assert_eq!(usage.api_calls, 1); +} + +#[test] +fn anthropic_response_without_thinking() { + let resp = json!({ + "id": "msg_02", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "text", + "text": "{\"label\": \"done\", \"done\": true, \"steps\": []}" + } + ], + "usage": { + "input_tokens": 500, + "output_tokens": 30 + } + }); + + let content = extract_assistant_content(&resp).unwrap(); + assert!(content.contains("done")); + assert!(extract_thinking_content(&resp).is_none()); +} + +#[test] +fn anthropic_tool_use_response() { + let resp = json!({ + "id": "msg_03", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "thinking", + "thinking": "I need to click the submit button." + }, + { + "type": "text", + "text": "I'll click the submit button for you." + }, + { + "type": "tool_use", + "id": "toolu_abc123", + "name": "Click", + "input": { + "selector": "button[type=submit]" + } + } + ], + "usage": { + "input_tokens": 800, + "output_tokens": 150 + } + }); + + // Text extracted + let content = extract_assistant_content(&resp).unwrap(); + assert!(content.contains("submit button")); + + // Thinking extracted + let thinking = extract_thinking_content(&resp).unwrap(); + assert!(thinking.contains("click the submit button")); + + // Tool calls parsed + let calls = parse_tool_calls(&resp); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].function.name, "Click"); + assert_eq!(calls[0].id, "toolu_abc123"); + + let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap(); + assert_eq!(args["selector"], "button[type=submit]"); +} + +// ── Unit: OpenAI reasoning_content response parsing ── + +#[test] +fn openai_reasoning_content_response() { + let resp = json!({ + "choices": [{ + "message": { + "content": "{\"label\": \"analyze\", \"done\": true, \"steps\": []}", + "reasoning_content": "Let me think about this step by step.\n\nFirst, I need to look at the HTML structure..." + } + }], + "usage": { + "prompt_tokens": 1000, + "completion_tokens": 200, + "total_tokens": 1200 + } + }); + + let content = extract_assistant_content(&resp).unwrap(); + assert!(content.contains("analyze")); + + let thinking = extract_thinking_content(&resp).unwrap(); + assert!(thinking.contains("step by step")); + + let usage = extract_usage(&resp); + assert_eq!(usage.prompt_tokens, 1000); + assert_eq!(usage.completion_tokens, 200); +} + +// ── Unit: Standard OpenAI response (no thinking) ── + +#[test] +fn openai_standard_response_no_regression() { + let resp = json!({ + "choices": [{ + "message": { + "content": "{\"label\": \"crawl\", \"done\": false, \"steps\": [{\"Click\": \"#next\"}]}" + } + }], + "usage": { + "prompt_tokens": 500, + "completion_tokens": 50, + "total_tokens": 550 + } + }); + + let content = extract_assistant_content(&resp).unwrap(); + assert!(content.contains("crawl")); + + // No thinking + assert!(extract_thinking_content(&resp).is_none()); + + let usage = extract_usage(&resp); + assert_eq!(usage.prompt_tokens, 500); + assert_eq!(usage.completion_tokens, 50); + + // No tool calls + assert!(parse_tool_calls(&resp).is_empty()); +} + +// ── Unit: endpoint detection ── + +#[test] +fn endpoint_detection() { + assert!(is_anthropic_endpoint( + "https://api.anthropic.com/v1/messages" + )); + assert!(!is_anthropic_endpoint( + "https://openrouter.ai/api/v1/chat/completions" + )); + assert!(!is_anthropic_endpoint( + "https://api.openai.com/v1/chat/completions" + )); + assert!(!is_anthropic_endpoint("http://localhost:8080/v1/chat")); +} + +// ── Unit: config thinking budget auto-resolution ── + +#[test] +fn thinking_budget_auto_resolves_from_reasoning_effort() { + use spider_agent_types::{ + effective_thinking_budget, effective_thinking_payload, ReasoningEffort, + RemoteMultimodalConfig, + }; + + // No reasoning configured → no thinking + let cfg = RemoteMultimodalConfig::default(); + assert!(effective_thinking_budget(&cfg).is_none()); + assert!(effective_thinking_payload(&cfg).is_none()); + + // reasoning_effort → auto-translated to thinking budget + let cfg = RemoteMultimodalConfig::default().with_reasoning_effort(Some(ReasoningEffort::High)); + assert_eq!(effective_thinking_budget(&cfg), Some(16384)); + + let pl = effective_thinking_payload(&cfg).unwrap(); + assert_eq!(pl["type"], "enabled"); + assert_eq!(pl["budget_tokens"], 16384); + + // Explicit thinking_budget takes priority + let cfg = RemoteMultimodalConfig::default() + .with_reasoning_effort(Some(ReasoningEffort::High)) + .with_thinking_budget(Some(50000)); + assert_eq!(effective_thinking_budget(&cfg), Some(50000)); +} + +// ── Unit: Anthropic response with multiple thinking blocks ── + +#[test] +fn anthropic_multiple_thinking_blocks() { + let resp = json!({ + "content": [ + {"type": "thinking", "thinking": "First, let me understand the problem."}, + {"type": "thinking", "thinking": "Now I'll formulate my approach."}, + {"type": "text", "text": "{\"label\": \"solution\"}"} + ] + }); + + let thinking = extract_thinking_content(&resp).unwrap(); + assert!(thinking.contains("understand the problem")); + assert!(thinking.contains("formulate my approach")); + // Blocks separated by newline + assert!(thinking.contains('\n')); +} + +// ── Unit: edge cases ── + +#[test] +fn empty_content_array_returns_none() { + let resp = json!({ + "content": [] + }); + assert!(extract_assistant_content(&resp).is_none()); + assert!(extract_thinking_content(&resp).is_none()); +} + +#[test] +fn anthropic_thinking_only_no_text() { + let resp = json!({ + "content": [ + {"type": "thinking", "thinking": "Just thinking..."} + ] + }); + // No text content + assert!(extract_assistant_content(&resp).is_none()); + // But thinking is captured + assert!(extract_thinking_content(&resp).is_some()); +} + +// ── Live integration test (gated) ── + +fn run_live_tests() -> bool { + matches!( + std::env::var("RUN_LIVE_TESTS") + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "1" | "true" | "yes" | "on" + ) +} + +/// Live test: extract data through OpenRouter with a thinking-capable model. +/// Validates the full pipeline: request building → API call → response parsing. +#[tokio::test] +async fn live_thinking_extraction_openrouter() { + if !run_live_tests() { + eprintln!("Skipping live thinking test (RUN_LIVE_TESTS not enabled)."); + return; + } + + dotenvy::dotenv().ok(); + + let api_key = std::env::var("OPEN_ROUTER") + .ok() + .filter(|v| !v.trim().is_empty()); + + let api_key = match api_key { + Some(k) => k, + None => { + eprintln!("Skipping: OPEN_ROUTER not set."); + return; + } + }; + + use spider_agent_types::{ReasoningEffort, RemoteMultimodalConfig}; + + let cfg = RemoteMultimodalConfig::default() + .with_reasoning_effort(Some(ReasoningEffort::Low)) + .with_extraction(true) + .with_extraction_prompt("Extract the page title and main heading"); + + let engine = spider_agent::automation::RemoteMultimodalEngine::new( + "https://openrouter.ai/api/v1/chat/completions", + "anthropic/claude-sonnet-4", + None, + ) + .with_api_key(Some(&api_key)) + .with_config(cfg); + + let html = r#"Test Page

Hello World

Some content.

"#; + + let result = engine + .extract_from_html(html, "https://example.com", Some("Test Page")) + .await; + + match result { + Ok(res) => { + assert!(res.success, "Extraction should succeed"); + assert!(res.extracted.is_some(), "Should have extracted data"); + let extracted = res.extracted.unwrap(); + let s = serde_json::to_string(&extracted).unwrap_or_default(); + // Should contain something about "Test Page" or "Hello World" + let lower = s.to_lowercase(); + assert!( + lower.contains("test") || lower.contains("hello"), + "Extracted data should reference page content: {s}" + ); + println!("Live test passed. Extracted: {s}"); + println!( + "Usage: prompt={} completion={} calls={}", + res.usage.prompt_tokens, res.usage.completion_tokens, res.usage.api_calls + ); + if let Some(reasoning) = &res.reasoning { + println!( + "Reasoning captured: {}...", + &reasoning[..reasoning.len().min(100)] + ); + } + } + Err(e) => { + // Rate limits or transient errors shouldn't fail the test suite + eprintln!("Live test error (non-fatal): {e}"); + } + } +} diff --git a/spider_agent_html/BUILD.bazel b/spider_agent_html/BUILD.bazel new file mode 100644 index 000000000..2f3c33b66 --- /dev/null +++ b/spider_agent_html/BUILD.bazel @@ -0,0 +1,25 @@ +""" +Spider Agent HTML - HTML processing utilities for spider_agent. +Cleaning, content analysis, and diffing. +""" + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "spider_agent_html", + srcs = glob(["src/**/*.rs"]), + edition = "2021", + deps = [ + "//spider_agent_types:spider_agent_types", + "@crates_io//:lol_html", + "@crates_io//:aho-corasick", + "@crates_io//:serde", + "@crates_io//:serde_json", + ], + rustc_flags = [ + "--cfg", + 'feature="serde"', + ], +) \ No newline at end of file diff --git a/spider_agent_html/Cargo.toml b/spider_agent_html/Cargo.toml new file mode 100644 index 000000000..61adc42ec --- /dev/null +++ b/spider_agent_html/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "spider_agent_html" +version = "2.47.22" +authors = ["j-mendez "] +description = "HTML processing utilities for spider_agent — cleaning, content analysis, and diffing." +repository = "https://github.com/spider-rs/spider" +readme = "README.md" +keywords = ["html", "cleaning", "web", "scraping", "agent"] +categories = ["web-programming", "text-processing"] +license = "MIT" +documentation = "https://docs.rs/spider_agent_html" +edition = "2021" + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +# HTML processing +lol_html = "2" +aho-corasick = "1" + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Types from our types crate +spider_agent_types = { version = "2.47.22", path = "../spider_agent_types" } + +[dev-dependencies] +serde_json = "1" diff --git a/spider_agent_html/README.md b/spider_agent_html/README.md new file mode 100644 index 000000000..59b60b6a7 --- /dev/null +++ b/spider_agent_html/README.md @@ -0,0 +1,102 @@ +# Spider Agent HTML + +Streaming HTML processing utilities for `spider_agent` — cleaning, content-aware profile selection, and intent-based optimization. + +## Overview + +This crate provides fast, single-pass HTML cleaning using `lol_html`'s streaming rewriter. No full DOM parsing required — O(n) processing with constant memory overhead. + +## Installation + +```toml +[dependencies] +spider_agent_html = "0.1" +``` + +## Quick Start + +```rust +use spider_agent_html::{clean_html_base, clean_html_slim, smart_clean_html}; + +let html = r#" +

Hello

World

..."#; + +// Base: remove scripts, styles, ads, tracking +let clean = clean_html_base(html); + +// Slim: also remove SVG, canvas, video, base64 +let slim = clean_html_slim(html); + +// Smart: auto-select the optimal profile based on content analysis +let smart = smart_clean_html(html); +``` + +## Cleaning Profiles + +| Profile | Removes | Use Case | +|---------|---------|----------| +| **Raw** | Nothing | Full HTML preservation | +| **Minimal** | `

Hello

"; + assert_eq!(clean_html_raw(html), html); + } + + #[test] + fn test_clean_html_base() { + let html = "

Hello

"; + let cleaned = clean_html_base(html); + assert!(!cleaned.contains(" + + + "#; + + let analysis = ContentAnalysis::analyze(html); + + assert!(analysis.has_dynamic_content); + assert!(analysis.is_thin_content); + } + + #[test] + fn test_quick_needs_screenshot() { + assert!(ContentAnalysis::quick_needs_screenshot( + "" + )); + assert!(ContentAnalysis::quick_needs_screenshot( + "" + )); + assert!(ContentAnalysis::quick_needs_screenshot("")); + assert!(ContentAnalysis::quick_needs_screenshot("short")); + + let long_text = "a".repeat(2000); + let html = format!("

{}

", long_text); + assert!(!ContentAnalysis::quick_needs_screenshot(&html)); + } + + #[test] + fn test_estimate_text_length() { + let html = "

Hello World

"; + let len = estimate_text_length(html); + assert_eq!(len, 10); // "HelloWorld" without spaces + } + + #[test] + fn test_aho_corasick_visual_elements() { + // Test Aho-Corasick matcher for visual elements + assert!(ContentAnalysis::has_visual_elements_quick( + "