# Ferrotype
> A tintype, also known as a melainotype or ferrotype, is a photograph made by creating a direct positive on a thin sheet of metal, colloquially called 'tin', coated with a dark lacquer or enamel and used as the support for the photographic emulsion.
Ferrotype is an opinionated wrapper around [insta.rs](https://insta.rs) that makes it easy to create and maintain structured snapshot tests with multiple named sections.
## Features
- **Multi-section snapshots** with automatically formatted titles
- **Rich content support** for various data types:
- Debug output (`add_debug`)
- Token streams (`add_token_stream`) with pretty formatting
- [Bluegum](https://docs.rs/bluegum) tree structures
- Hex dumps of binary data with `hex` crate
- ANSI color code stripping
- **Deterministic output** with automatic filtering of memory addresses and non-deterministic data
- **Flexible organization** with configurable snapshot directory structure
- **Debugging support** with backtraces and error handling
- **Seamless integration** with existing insta.rs workflows
## Installation
Add this to your `Cargo.toml`:
```toml
[dev-dependencies]
ferrotype = "0.1"
```
## Quick Start
Here's the minimum required code to create a ferrotype snapshot:
```rust
use ferrotype::Ferrotype;
// 1. Create a new snapshot
let mut snapshot = Ferrotype::new();
// 2. Add content to sections
snapshot.add("Input", "Hello, world!".to_string());
// 3. Assert the snapshot
ferrotype::assert!(snapshot);
```
## Comprehensive Example
```rust
use ferrotype::Ferrotype;
#[derive(Debug)]
struct TestData {
name: String,
count: u32,
items: Vec<String>,
}
let mut snapshot = Ferrotype::new();
// Configure snapshot behavior
snapshot.set_filter_memory_addresses(true); // Default: true
snapshot.set_expect_errors(false); // Default: false
// Add different types of content
snapshot.add("Description", "Testing my awesome feature".to_string());
let test_data = TestData {
name: "test".to_string(),
count: 42,
items: vec!["a".to_string(), "b".to_string()],
};
snapshot.add_debug("Test Data", &test_data);
// Add hex dump of binary data
#[cfg(feature = "hex")]
snapshot.add_hex("Binary Data", &[0xDE, 0xAD, 0xBE, 0xEF]);
// Add formatted Rust code
#[cfg(feature = "tokenstream")]
{
let tokens: proc_macro2::TokenStream =
"fn hello() { println!(\"Hello!\"); }".parse().unwrap();
snapshot.add_token_stream("Generated Code", &tokens);
}
// Assert the snapshot
ferrotype::assert!(snapshot);
```
## Examples
The crate includes several example programs demonstrating different aspects of ferrotype:
- **`basic`** - Core functionality and simple usage patterns
- **`filtering`** - Demonstrates all filtering options for non-deterministic data
- **`features`** - Shows feature-specific functionality (hex dumps, token streams, etc.)
- **`testing`** - Real-world testing scenarios with an API client
Run examples with:
```bash
# Basic example
cargo run --example basic
# Filtering demonstration
cargo run --example filtering
# All features (requires all features enabled)
cargo run --example features --all-features
# Testing example
cargo run --example testing
```
## Snapshot Output Format
The above example would create a snapshot file like this:
```yaml
---
source: src/lib.rs
expression: "use my_crate::comprehensive_example"
---
Description: >
Testing my awesome feature
TestData: >
TestData {
name: "test",
count: 42,
items: [
"a",
"b",
],
}
BinaryData: >
Length: 4 (0x4) bytes
0000: de ad be ef ....
GeneratedCode: >
fn hello() {
println!("Hello!");
}
Backtrace: >
0: my_crate::comprehensive_example
at src/lib.rs:42:5
1: my_crate::comprehensive_example::{{closure}}
at src/lib.rs:41:1
...
```
## Available Methods
### Core Methods
- `Ferrotype::new()` - Create a new snapshot builder
- `add(title, content)` - Add a section with string content
- `add_debug(title, item)` - Add a section with debug-formatted content
- `as_string()` - Get the formatted snapshot content
- `print()` - Print to stdout
### Configuration
- `set_expect_errors(bool)` - Configure whether errors are expected
- `set_filter_memory_addresses(bool)` - Toggle memory address filtering
- `add_backtrace()` - Add current call stack to snapshot
### Feature-specific Methods
These methods are available when the corresponding features are enabled:
#### `bluegum` feature
- `add_bluegum(title, tree)` - Add a rendered bluegum tree
- `add_bluegum_builder(title, builder)` - Add a pre-built bluegum structure
- `add_bluegum_builder_with(title, closure)` - Build bluegum structure with closure
- `add_bluegum_with(title, tree, styles)` - Add bluegum tree with custom styles
#### `tokenstream` feature
- `add_token_stream(title, tokens)` - Add formatted Rust code from TokenStream
#### `anstream` feature
- `add_strip_str(title, content)` - Add content with ANSI codes stripped
#### `hex` feature
- `add_hex(title, bytes)` - Add hex dump of binary data
## Configuration Options
### Snapshot Directory
Control where snapshots are stored:
```rust,ignore
// Use custom folder structure
#[use_folder(parser, tests)]
ferrotype::assert!(snapshot);
// Results in snapshots stored in: snapshots/parser/tests/
```
### Feature Flags
```toml
[dev-dependencies]
ferrotype = { version = "0.1", features = ["hex", "bluegum"] }
# Or disable default features and pick specific ones
ferrotype = { version = "0.1", default-features = false, features = ["tokenstream"] }
```
Available features:
- `dot_snapshots` - Store snapshots in `.snapshots/` instead of `snapshots/`
- `tokenstream` - Enable `add_token_stream()` for formatting Rust code
- `bluegum` - Enable bluegum tree rendering methods
- `anstream` - Enable `add_strip_str()` for ANSI code removal
- `hex` - Enable `add_hex()` for binary data hex dumps
All features are enabled by default.
### Error Handling
```rust,no_run
use ferrotype::Ferrotype;
let mut snapshot = Ferrotype::new();
snapshot.set_expect_errors(true); // Don't fail test on errors
snapshot.add("Error Case", "Some error output".to_string());
ferrotype::assert!(snapshot);
```
### Memory Address Filtering
```rust,no_run
use ferrotype::Ferrotype;
#[derive(Debug)]
struct SomeStruct {
value: i32,
}
let some_struct = SomeStruct { value: 42 };
let mut snapshot = Ferrotype::new();
snapshot.set_filter_memory_addresses(false); // Preserve memory addresses
snapshot.add_debug("Raw Debug", &some_struct);
ferrotype::assert!(snapshot);
```
## Integration with Existing Tests
Ferrotype works alongside existing insta.rs tests:
```rust,no_run
use ferrotype::Ferrotype;
// Existing insta test
fn existing_test() {
let my_data = vec![1, 2, 3];
insta::assert_debug_snapshot!(my_data);
}
// New ferrotype test
fn enhanced_test() {
let input_data = "test input";
let output_data = vec!["result1", "result2"];
let analysis_results = "analysis complete";
let mut snapshot = Ferrotype::new();
snapshot.add("Input", input_data.to_string());
snapshot.add_debug("Output", &output_data);
snapshot.add("Analysis", analysis_results.to_string());
ferrotype::assert!(snapshot);
}
```
## Best Practices
1. **Use descriptive section titles**: They become the headings in your snapshots
2. **Group related data**: Put related information in the same test
3. **Enable memory filtering**: Keeps snapshots deterministic across runs
4. **Use appropriate content methods**: `add_debug` for structs, `add_hex` for binary data, etc.
5. **Review snapshots**: Use `cargo insta review` to accept/reject changes
## License
Licensed under the Blue Oak Model License 1.0.0 - see the [LICENSE](LICENSE) file for details.