Content-addressed URL fetcher with sha256 dedup, per-host rate limits, and reqwest→bridge executor fallback
| Tier | infrastructure |
| Role | unclassified (baselined) |
| Path | crates/infrastructure/fetcher |
| Edition | 2021 |
| Targets | infrastructure_fetcher, httpbin, safe_http_integration |
| Public items | 0 across 0 modules |
| Tests | 124 |
What it is for
infrastructure-fetcher: content-addressed URL fetching with scope-enforced dedup.
Given a URL and routing/validation hints, Fetcher drives an executor stack (reqwest → bridge → …), content-addresses the response by sha256, respects per-host rate limits, and writes the bytes to infrastructure_storage exactly once per (scope, content) pair.
Layer
Infrastructure. Depends on infrastructure_storage and operations_rate_limit.
Key types
Fetcher— entry point; construct viaFetcherBuilder.TenantScopedFetcher— tenant-bound handle, dedup scoped per tenant.FetchHints—{ routing: RoutingHints, validation: ValidationPolicy }.FetchOutcome—{ stored_file, fetch_status, executor_used, final_url }.
See docs/userstories/sprint-0.1-fetcher.md for the full spec + Gate 1.5 consensus log.
Capabilities
No public items.
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
No public items.
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
BridgeExecutor | executors::bridge::BridgeExecutor |
Fetcher | fetcher::Fetcher |
FetcherBuilder | builder::FetcherBuilder |
ReqwestExecutor | executors::reqwest::ReqwestExecutor |
TenantScopedFetcher | tenant_scoped::TenantScopedFetcher |
{AttemptOutcome,AutoFallbackSelector,ExecutorSelector,SelectionContext,SelectionPlan,} | selector::{AttemptOutcome,AutoFallbackSelector,ExecutorSelector,SelectionContext,SelectionPlan,} |
{Executor,ExecutorResponse} | executor::{Executor,ExecutorResponse} |
{ExecutorError,FetchError,FetcherBuildError,NetworkError} | errors::{ExecutorError,FetchError,FetcherBuildError,NetworkError} |
{ExecutorKind,FetchOutcome,FetchStatus} | outcome::{ExecutorKind,FetchOutcome,FetchStatus} |
{ExecutorPreference,FetchHints,RateLimitScope,ResponseMeta,RoutingHints,TargetKind,ValidationPolicy,} | hints::{ExecutorPreference,FetchHints,RateLimitScope,ResponseMeta,RoutingHints,TargetKind,ValidationPolicy,} |
{is_safe_ip,is_safe_url,SsrfError} | netguard::{is_safe_ip,is_safe_url,SsrfError} |
{read_body_capped,InternalEndpoints,InternalHttpClientBuilder,SafeHttpClient,SafeHttpClientBuilder,SafeHttpError,DEFAULT_MAX_REDIRECTS,DEFAULT_TIMEOUT,} | safe_http::{read_body_capped,InternalEndpoints,InternalHttpClientBuilder,SafeHttpClient,SafeHttpClientBuilder,SafeHttpError,DEFAULT_MAX_REDIRECTS,DEFAULT_TIMEOUT,} |
Boundary
Reaches into operations.
Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, … (82 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | infrastructure |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/infrastructure/fetcher |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_infrastructure["infrastructure"] --> n_operations["operations"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-storage` | infrastructure | no | always |
| `operations-rate-limit` | operations | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
bytes | ^1 | — | no | always |
chrono | ^0.4 | serde | no | always |
futures-util | ^0.3 | — | no | always |
hex | ^0.4 | — | no | always |
http | ^1 | — | no | always |
mime | ^0.3 | — | no | always |
reqwest | ^0.12 | json, rustls-tls, stream | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sha2 | ^0.10 | — | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full, fs, net, sync | no | always |
tracing | ^0.1 | — | no | always |
url | ^2 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
reqwest | ^0.12 | gzip | no | always |
tempfile | ^3 | — | no | always |
tokio | ^1 | full, full, test-util | no | always |
wiremock | ^0.6 | — | no | always |
Build. None.
Depended on by. 29 workspace crates.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR consumers["29 consumer crates"] -->|uses| SELF SELF["infrastructure-fetcher"] SELF -->|runtime| runtime_deps["2 workspace crates"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
test-util | — | no |
flowchart LR n_test_util["test-util"]
Targets
| Kind | Name | Source |
|---|---|---|
| lib | infrastructure_fetcher | `src/lib.rs` |
| test | httpbin | `tests/httpbin.rs` |
| test | safe_http_integration | `tests/safe_http_integration.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | yes |
| database access | none detected |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
29 workspace crates depend on this one: application-fetcher, application-party-discovery, application-wiki, content-assets, infrastructure-acquire, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, infrastructure-ai, … (29 total).
Verification
| Kind | Count |
|---|---|
| Unit tests | 114 |
| Integration tests | 10 |
| Examples | 0 |
| Doctests | 0 |
What the tests establish, by name:
httpbin_round_trip_downloads_and_dedups—tests/httpbin.rsegress_default_still_refuses_loopback_literal—tests/safe_http_integration.rsinternal_client_reaches_allowlisted_loopback_exact_port—tests/safe_http_integration.rsinternal_client_refuses_non_allowlisted_port_on_listed_host—tests/safe_http_integration.rsinternal_client_stays_strict_for_non_allowlisted_internal_host—tests/safe_http_integration.rspreflight_rejects_metadata_address_and_non_http_scheme—tests/safe_http_integration.rsread_body_capped_aborts_when_body_exceeds_cap—tests/safe_http_integration.rsread_body_capped_returns_body_within_cap—tests/safe_http_integration.rssafe_client_refuses_localhost_via_pinned_resolver—tests/safe_http_integration.rssafe_client_refuses_loopback_literal_with_ssrf_error—tests/safe_http_integration.rsbuilder_rejects_bare_build_with_no_general_purpose_executor—src/builder.rsbuilder_rejects_duplicate_executors_with_duplicate_executor—src/builder.rsbuilder_rejects_empty_executor_list_with_no_general_purpose—src/builder.rsbuilder_rejects_missing_rate_limiter_with_rate_limiter_required—src/builder.rsbuilder_rejects_missing_storage_with_storage_required—src/builder.rsbuilder_valid_build_succeeds—src/builder.rsbuilder_with_bridge_and_reqwest_both_register—src/builder.rsbuilder_with_executor_escape_hatch_accepts_custom_impl—src/builder.rsbuilder_with_selector_overrides_default—src/builder.rsexecutor_error_blocked_or_challenged_includes_reason—src/errors.rsexecutor_error_converts_into_fetch_error—src/errors.rsexecutor_error_http_status_includes_code_and_message—src/errors.rsexecutor_error_not_available_names_the_kind—src/errors.rsfetch_error_cancelled_displays_cleanly—src/errors.rsfetch_error_content_type_mismatch_names_both_sides—src/errors.rsfetch_error_rate_limited_display_is_informative—src/errors.rsfetch_error_too_large_shows_limit_and_actual—src/errors.rsfetch_error_validation_failed_includes_reason—src/errors.rsfetcher_build_error_duplicate_executor_names_kind—src/errors.rsfetcher_build_error_no_general_purpose_mentions_reqwest_or_bridge—src/errors.rs- _… 94 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 0 | 0 |
Public modules with a //! block | 0 | 0 |
Metrics
| Metric | Value |
|---|---|
| Rust source files | 15 |
| Source lines | 4304 |
| Code lines | 3010 |
| Public API items | 0 |
| Public modules | 0 |
| Tests | 124 |
| Examples | 0 |
| Cargo features | 1 |
| Direct runtime dependencies | 18 |
| Workspace reverse dependencies | 29 |
pie showData
title Rust source composition
"Code" : 3010
"Blank or comment" : 1294
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.