diffo 0.1.0

Semantic diffing for Rust structs via serde
Documentation

diffo

Semantic diffing for Rust structs via serde.

Crates.io Documentation License

Features

  • 🚀 Zero boilerplate - works with any Serialize type
  • ðŸŽŊ Path-based changes - user.roles[2].name
  • ðŸŽĻ Multiple formatters - pretty, JSON, JSON Patch (RFC 6902), Markdown
  • 🔒 Secret masking - hide sensitive fields
  • 🔧 Configurable - float tolerance, depth limits, collection caps
  • ⚡ Fast - optimized for production use

Quick Start

use diffo::diff;
use serde::Serialize;

#[derive(Serialize)]
struct Config {
    version: String,
    port: u16,
}

let old = Config { version: "1.0".into(), port: 8080 };
let new = Config { version: "1.1".into(), port: 8080 };

let d = diff(&old, &new).unwrap();
println!("{}", d.to_pretty());

Output:

version
  - String("1.0")
  + String("1.1")

Installation

Add to your Cargo.toml:

[dependencies]
diffo = "0.1"

Examples

Basic Usage

use diffo::diff;
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

let old = User {
    id: 1,
    name: "Alice".into(),
    email: "alice@old.com".into()
};

let new = User {
    id: 1,
    name: "Alice Smith".into(),
    email: "alice@new.com".into()
};

let diff = diff(&old, &new).unwrap();

// Check specific changes
assert!(diff.get("name").is_some());
assert!(diff.get("email").is_some());
assert!(diff.get("id").is_none());

Masking Secrets

use diffo::{diff_with, DiffConfig};

let config = DiffConfig::new()
    .mask("*.password")
    .mask("api.secret");

let diff = diff_with(&old, &new, &config).unwrap();

Float Tolerance

use diffo::DiffConfig;

let config = DiffConfig::new()
    .float_tolerance("metrics.*", 1e-6)
    .default_float_tolerance(1e-9);

let diff = diff_with(&old, &new, &config).unwrap();

Output Formats

let diff = diff(&old, &new).unwrap();

// Pretty format (human-readable)
println!("{}", diff.to_pretty());

// JSON format
let json = diff.to_json()?;

// JSON Patch (RFC 6902)
let patch = diff.to_json_patch()?;

// Markdown table (great for PRs)
let markdown = diff.to_markdown()?;

JSON Patch Output:

[
  {
    "op": "replace",
    "path": "/name",
    "value": "Alice Smith"
  },
  {
    "op": "replace",
    "path": "/email",
    "value": "alice@new.com"
  }
]

Markdown Output:

Path Change Old Value New Value
name Modified "Alice" "Alice Smith"
email Modified "alice@old.com" "alice@new.com"

Use Cases

  • Config changes - Track configuration drift
  • Audit logs - Record what changed in entities
  • API testing - Compare expected vs actual responses
  • Migration tools - Validate data transformations
  • Database migrations - Verify schema changes
  • CI/CD - Detect unintended changes

Advanced Configuration

use diffo::DiffConfig;

let config = DiffConfig::new()
    // Ignore specific paths
    .ignore("*.internal")
    .ignore("metadata.timestamp")

    // Mask sensitive data
    .mask("*.password")
    .mask("*.secret")

    // Float comparison tolerance
    .float_tolerance("metrics.*.value", 1e-6)
    .default_float_tolerance(1e-9)

    // Limit depth (prevent stack overflow)
    .max_depth(32)

    // Limit collection size
    .collection_limit(500);

let diff = diff_with(&old, &new, &config)?;

Edge Cases Handled

  • Floats: NaN == NaN, -0.0 == +0.0
  • Large collections: Automatic elision with configurable limits
  • Large byte arrays: Hex previews instead of full dumps
  • Deep nesting: Configurable depth limits
  • Type mismatches: Clear reporting when types differ

Performance

Diffo is designed for production use:

  • O(n) complexity for most operations (n = number of fields)
  • Index-based sequence diffing (not LCS, which is O(nÂē))
  • Efficient path representation with BTreeMap
  • Zero-copy where possible

Typical performance (on modest hardware):

  • Small struct (5 fields): ~1-2 Ξs
  • Medium struct (50 fields): ~10-20 Ξs
  • Large struct (500 fields): ~100-200 Ξs

Comparison with Alternatives

Feature diffo serde-diff json-diff
Path notation ✅ ❌ ✅
Secret masking ✅ ❌ ❌
JSON Patch (RFC 6902) ✅ ❌ ✅
Float tolerance ✅ ❌ ❌
Works with any Serialize ✅ ✅ JSON only
Multiple formatters ✅ ❌ ❌
Collection limits ✅ ❌ ❌

Roadmap

v0.2

  • LCS-based sequence diffing (patience algorithm)
  • Configurable sequence diff strategies
  • Custom comparator functions per path

v0.3

  • Path interning for performance
  • Streaming diff for very large structures
  • Diff application (apply changes to produce new value)

v1.0

  • Stable API guarantee
  • Comprehensive benchmarks vs alternatives
  • Performance optimization guide

Contributing

Contributions are welcome! Please open an issue or PR on GitHub.

License

Licensed under either of:

at your option.

Acknowledgments

Built with: