{"id":31045906,"url":"https://github.com/colonelpanic8/http-client-vcr","last_synced_at":"2026-01-20T17:37:21.287Z","repository":{"id":308122817,"uuid":"1031681107","full_name":"colonelpanic8/http-client-vcr","owner":"colonelpanic8","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-05T21:26:55.000Z","size":315,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-08T08:56:32.966Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/colonelpanic8.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-04T07:09:46.000Z","updated_at":"2025-08-05T21:26:57.000Z","dependencies_parsed_at":"2025-08-04T11:10:28.246Z","dependency_job_id":null,"html_url":"https://github.com/colonelpanic8/http-client-vcr","commit_stats":null,"previous_names":["colonelpanic8/http-client-vcr"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/colonelpanic8/http-client-vcr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colonelpanic8%2Fhttp-client-vcr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colonelpanic8%2Fhttp-client-vcr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colonelpanic8%2Fhttp-client-vcr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colonelpanic8%2Fhttp-client-vcr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/colonelpanic8","download_url":"https://codeload.github.com/colonelpanic8/http-client-vcr/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colonelpanic8%2Fhttp-client-vcr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275143585,"owners_count":25413091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-14T02:00:10.474Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-14T17:47:19.098Z","updated_at":"2026-01-20T17:37:21.281Z","avatar_url":"https://github.com/colonelpanic8.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HTTP Client VCR\n\nA Rust library for recording and replaying HTTP requests, inspired by VCR libraries in other languages. This library works with the `http-client` crate to provide a simple way to test HTTP interactions.\n\n## Features\n\n- **Record HTTP interactions** to YAML cassettes\n- **Replay recorded interactions** for deterministic tests\n- **Multiple recording modes** (Record, Replay, Once, None)\n- **Flexible request matching** (URL, method, headers, body)\n- **Builder pattern** for easy configuration\n- **Thread-safe** with async support\n\n## Usage\n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nhttp-client-vcr = \"0.1.0\"\n```\n\n### Basic Example\n\n```rust\nuse http_client_vcr::{VcrClient, VcrMode};\nuse http_client::Request;\nuse http_types::{Method, Url};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Create your HTTP client (h1, hyper, isahc, etc.)\n    let inner_client = Box::new(h1::H1Client::new());\n    \n    // Create VCR client\n    let vcr_client = VcrClient::builder()\n        .inner_client(inner_client)\n        .cassette_path(\"fixtures/my_test.yaml\")\n        .mode(VcrMode::Once)\n        .build()\n        .await?;\n    \n    // Use it like any HttpClient\n    let request = Request::new(Method::Get, Url::parse(\"https://httpbin.org/get\")?);\n    let response = vcr_client.send(request).await?;\n    \n    println!(\"Status: {}\", response.status());\n    \n    // Save cassette\n    vcr_client.save_cassette().await?;\n    \n    Ok(())\n}\n```\n\n## VCR Modes\n\n- **`VcrMode::Record`**: Always make real HTTP requests and record them. If an interaction already exists in the cassette, replay it instead.\n- **`VcrMode::Replay`**: Only replay interactions from the cassette. Fail if no matching interaction is found.\n- **`VcrMode::Once`**: Record interactions only if the cassette is empty, otherwise replay existing interactions.\n- **`VcrMode::None`**: Pass through to the inner HTTP client without any recording or replaying.\n\n## Request Matching\n\nBy default, requests are matched by HTTP method and URL. You can customize matching behavior:\n\n```rust\nuse http_client_vcr::{DefaultMatcher, ExactMatcher};\n\n// Custom matching (method, URL, specific headers)\nlet matcher = DefaultMatcher::new()\n    .with_method(true)\n    .with_url(true)\n    .with_headers(vec![\"Authorization\".to_string(), \"Content-Type\".to_string()]);\n\nlet vcr_client = VcrClient::builder()\n    .inner_client(inner_client)\n    .cassette_path(\"fixtures/my_test.yaml\")\n    .matcher(Box::new(matcher))\n    .build()\n    .await?;\n```\n\n## Filtering Sensitive Data\n\nVCR supports filtering sensitive data from requests and responses before they are stored in cassettes:\n\n### Built-in Filters\n\n```rust\nuse http_client_vcr::{HeaderFilter, BodyFilter, UrlFilter, FilterChain};\n\n// Remove sensitive headers\nlet header_filter = HeaderFilter::new()\n    .remove_auth_headers()  // Removes Authorization, Cookie, X-API-Key, etc.\n    .remove_header(\"X-Custom-Secret\")\n    .replace_header(\"User-Id\", \"FILTERED\");\n\n// Filter JSON body content\nlet body_filter = BodyFilter::new()\n    .remove_common_sensitive_keys()  // Removes password, token, api_key, etc.\n    .remove_json_key(\"credit_card\")\n    .replace_regex(r\"\\d{4}-\\d{4}-\\d{4}-\\d{4}\", \"XXXX-XXXX-XXXX-XXXX\")\n    .unwrap();\n\n// Filter URL query parameters\nlet url_filter = UrlFilter::new()\n    .remove_common_sensitive_params()  // Removes api_key, token, etc.\n    .remove_query_param(\"secret\")\n    .replace_query_param(\"user_id\", \"FILTERED\");\n\n// Chain filters together\nlet filter_chain = FilterChain::new()\n    .add_filter(Box::new(header_filter))\n    .add_filter(Box::new(body_filter))\n    .add_filter(Box::new(url_filter));\n\nlet vcr_client = VcrClient::builder()\n    .inner_client(inner_client)\n    .cassette_path(\"fixtures/filtered_test.yaml\")\n    .filter_chain(filter_chain)\n    .build()\n    .await?;\n```\n\n### Custom Filters\n\nYou can create custom filters for more complex scenarios:\n\n```rust\nuse http_client_vcr::CustomFilter;\n\nlet custom_filter = CustomFilter::new(|req, resp| {\n    // Remove any header containing \"secret\"\n    req.headers.retain(|key, _| !key.to_lowercase().contains(\"secret\"));\n    \n    // Replace response body if it contains errors\n    if let Some(body) = \u0026mut resp.body {\n        if body.contains(\"error\") {\n            *body = r#\"{\"error\": \"FILTERED\"}\"#.to_string();\n        }\n    }\n});\n\nlet vcr_client = VcrClient::builder()\n    .inner_client(inner_client)\n    .add_filter(Box::new(custom_filter))\n    .build()\n    .await?;\n```\n\n**Important: Filters are applied only to the data stored in cassette files, not to the actual HTTP interactions.** During recording:\n\n1. **Real requests** are made with original sensitive data (so APIs work properly)\n2. **Real responses** are returned to your application (unfiltered)  \n3. **Filtered copies** are stored in the cassette (removing sensitive data)\n\nThis ensures your code gets the real data it needs while keeping cassettes safe for version control.\n\n## NoOp Client for Testing\n\nFor ultimate safety during testing, VCR provides a `NoOpClient` that ensures no real HTTP requests are ever made:\n\n```rust\nuse http_client_vcr::{VcrClient, VcrMode, NoOpClient};\n\n// Guarantee no real HTTP requests can be made\nlet vcr_client = VcrClient::builder()\n    .inner_client(Box::new(NoOpClient::new()))\n    .cassette_path(\"tests/fixtures/api_test.yaml\")\n    .mode(VcrMode::Replay) // Only replay from cassette\n    .build()\n    .await?;\n\n// This works if the request exists in the cassette\nlet response = vcr_client.send(request).await?;\n// If not in cassette, you get a clear error (not a real HTTP request)\n```\n\nTwo variants are available:\n\n- **`NoOpClient::new()`** - Returns an error if a request is attempted\n- **`NoOpClient::panicking()`** - Panics with a stack trace (useful for development)\n\nThis is particularly useful in CI/CD environments or when you want to be absolutely certain your tests are deterministic.\n\n## Cassette Format\n\nCassettes are stored as YAML files with the following structure:\n\n```yaml\ninteractions:\n  - request:\n      method: GET\n      url: https://httpbin.org/get\n      headers:\n        User-Agent: [\"http-client/1.0\"]\n      body: null\n      version: Http1_1\n    response:\n      status: 200\n      headers:\n        Content-Type: [\"application/json\"]\n      body: '{\"origin\": \"127.0.0.1\"}'\n      version: Http1_1\n```\n\n## Testing with VCR\n\nVCR is particularly useful for testing:\n\n```rust\n#[tokio::test]\nasync fn test_api_call() {\n    let vcr_client = VcrClient::builder()\n        .inner_client(Box::new(h1::H1Client::new()))\n        .cassette_path(\"tests/fixtures/api_call.yaml\")\n        .mode(VcrMode::Once)\n        .build()\n        .await\n        .unwrap();\n    \n    let request = Request::new(Method::Get, Url::parse(\"https://api.example.com/data\").unwrap());\n    let response = vcr_client.send(request).await.unwrap();\n    \n    assert_eq!(response.status(), 200);\n    \n    // First run records the interaction\n    // Subsequent runs replay from cassette\n}\n```\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolonelpanic8%2Fhttp-client-vcr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcolonelpanic8%2Fhttp-client-vcr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolonelpanic8%2Fhttp-client-vcr/lists"}