interface 0.0.3

Typed, lossy-aware interface translation between API versions
Documentation
# `interface` *(WIP)*

Typed, lossy-aware interface translation between API versions.

## Overview

`interface` models data migration between versioned API schemas. Each
conversion is a [`Translation<Source, Target, Lossiness>`] — a deferred,
inspectable value that carries a [`Diff`] and encodes at the type level
whether the conversion is information-preserving ([`Lossless`]) or
destructive ([`Lossy`]).

## Usage

### Implement `Upgrade` / `Downgrade`

```rust
use interface::{Diff, Lossless, Lossy, Translation, Upgrade, Downgrade};

mod v1 {
    #[derive(Debug, PartialEq, Eq)]
    pub struct User { pub name: String }
}

mod v2 {
    #[derive(Debug, PartialEq, Eq)]
    pub struct User { pub name: String, pub email: String }
}

impl Upgrade<v2::User> for v1::User {
    type Lossiness = Lossless;

    fn upgrade(self) -> Translation<Self, v2::User, Lossless> {
        let diff = Diff::new().add("email", "default@example.com");
        Translation::new(self, Box::new(|s| v2::User {
            name: s.name,
            email: "default@example.com".into(),
        }), diff)
    }
}

impl Downgrade<v1::User> for v2::User {
    type Lossiness = Lossy;

    fn downgrade(self) -> Translation<Self, v1::User, Lossy> {
        let diff = Diff::new().sub("email", &self.email);
        Translation::new(self, Box::new(|s| v1::User { name: s.name }), diff)
    }
}
```

### Execute a translation

```rust
// Lossless: `.translate()` is only available on `Translation<_, _, Lossless>`
let t = v1::User { name: "Alice".into() }.upgrade();
assert!(!t.is_lossy());
let v2_user = t.translate();

// Lossy: must call `.translate_lossy()` — the distinct method name forces acknowledgement
let t = v2_user.downgrade();
assert!(t.is_lossy());
println!("{t}");             // prints the diff
let v1_user = t.translate_lossy();
```

## License

MIT