ReqCassette.BodyType (ReqCassette v0.5.1)
View SourceDetects and handles different body types for optimal cassette storage.
This module provides intelligent body type detection and encoding/decoding to ensure cassettes are human-readable, compact, and easy to edit. It distinguishes between three body types based on content-type headers and content analysis.
Body Types
:json - JSON Data
JSON responses are stored as native Elixir data structures in the body_json field.
When the cassette is saved, Jason pretty-prints the JSON for readability.
Benefits:
- No double-encoding or escaping
- Compact cassette files
- Human-readable without string escape sequences
- Easy to manually edit cassettes
Example storage:
"body_json": {
"id": 1,
"name": "Alice",
"roles": ["admin", "user"]
}:text - Plain Text
Text responses (HTML, XML, CSV, plain text) are stored as strings in the body field.
Examples:
- HTML pages
- XML documents
- CSV data
- Plain text files
- YAML/TOML configs
Example storage:
"body": "<html><head><title>Page</title></head><body>...</body></html>":blob - Binary Data
Binary responses (images, PDFs, videos) are base64-encoded in the body_blob field.
Examples:
- PNG/JPEG images
- PDF documents
- ZIP archives
- Protocol buffers
- MessagePack data
Example storage:
"body_blob": "iVBORw0KGgoAAAANSUhEUgAAAAUA..."Detection Algorithm
The module uses a multi-step detection process:
Content-Type Header - Check for explicit type hints
application/json→:jsontext/*→:textimage/*→:blob
Already Decoded - If Req already decoded the body to a map/list →
:jsonJSON Parsing - Attempt to parse as JSON, if successful →
:jsonPrintability Check - Use
String.printable?/1:- Printable →
:text - Non-printable →
:blob
- Printable →
This ensures accurate detection even when content-type headers are missing or incorrect.
Usage
This module is used internally by ReqCassette.Cassette when adding interactions:
# Automatic type detection and encoding
body_type = BodyType.detect_type(response.body, response.headers)
{field, value} = BodyType.encode(response.body, body_type)
# Later, when replaying
decoded_body = BodyType.decode(cassette_response)You typically don't need to use this module directly - it's called automatically by the cassette system.
Examples
# JSON Detection
detect_type(~s({"key": "value"}), %{"content-type" => ["application/json"]})
#=> :json
detect_type(%{"id" => 1}, %{}) # Already decoded by Req
#=> :json
# Text Detection
detect_type("<html><body>Hello</body></html>", %{"content-type" => ["text/html"]})
#=> :text
detect_type("name,age\nAlice,30", %{"content-type" => ["text/csv"]})
#=> :text
# Blob Detection
detect_type(<<137, 80, 78, 71, 13, 10, 26, 10>>, %{"content-type" => ["image/png"]})
#=> :blob
detect_type(<<255, 216, 255, 224>>, %{}) # Non-printable = blob
#=> :blob
# Encoding for storage
encode(%{"id" => 1, "name" => "Alice"}, :json)
#=> {"body_json", %{"id" => 1, "name" => "Alice"}}
encode("<html></html>", :text)
#=> {"body", "<html></html>"}
encode(<<137, 80, 78, 71>>, :blob)
#=> {"body_blob", "iVBORw=="}
# Decoding from cassette
decode(%{"body_type" => "json", "body_json" => %{"id" => 1}})
#=> ~s({"id":1})
decode(%{"body_type" => "text", "body" => "<html></html>"})
#=> "<html></html>"
decode(%{"body_type" => "blob", "body_blob" => "iVBORw=="})
#=> <<137, 80, 78, 71>>
Summary
Functions
Decodes body from cassette storage.
Detects the body type from content and headers.
Encodes body for cassette storage based on its type.
Types
Functions
Decodes body from cassette storage.
Parameters
cassette_response- The response map from cassette with body_type and body fields
Returns
Decoded body as binary string
Examples
decode(%{"body_type" => "json", "body_json" => %{"id" => 1}})
# => ~s({"id":1})
decode(%{"body_type" => "text", "body" => "<html></html>"})
# => "<html></html>"
decode(%{"body_type" => "blob", "body_blob" => "iVBORw0K..."})
# => <<137, 80, 78, 71, ...>>
Detects the body type from content and headers.
Detection algorithm:
- Check content-type header for hints
- For empty bodies, return :text
- Try parsing as JSON
- Check if string is printable (text vs binary)
- Default to :blob for binary data
Parameters
body- The body content (string or binary)headers- HTTP headers map (lowercase keys)
Returns
Body type: :json, :text, or :blob
Examples
detect_type("", %{})
# => :text
detect_type(~s({"id": 1}), %{"content-type" => ["application/json"]})
# => :json
detect_type("<html></html>", %{"content-type" => ["text/html"]})
# => :text
detect_type(<<137, 80, 78, 71>>, %{"content-type" => ["image/png"]})
# => :blob
Encodes body for cassette storage based on its type.
Parameters
body- The body contentbody_type- The detected body type (:json,:text, or:blob)
Returns
Tuple of {field_name, encoded_value} where:
:json→{"body_json", decoded_map_or_list}:text→{"body", string}:blob→{"body_blob", base64_string}
Examples
encode(~s({"id": 1}), :json)
# => {"body_json", %{"id" => 1}}
encode("<html></html>", :text)
# => {"body", "<html></html>"}
encode(<<137, 80, 78, 71>>, :blob)
# => {"body_blob", "iVBORw0K..."}