# result-transformer
[](https://crates.io/crates/result-transformer)
[](https://docs.rs/result-transformer)
[](https://github.com/result-transformer/result-transformer-rs)
> **Composable, type‑safe transforms for `Result<T, E>` – with matching *sync* and *async* APIs.**
`result-transformer` lets you describe how a `Result` should be rewritten **without coupling that logic to your domain
types or execution model**.
Implement a tiny trait – or let a one‑line macro do it for you – and drop the transformer anywhere you need it.
Current version is `0.0.2`. The API is not yet stable and may change.
## Table of contents
- [Why use result-transformer?](#why-use-result-transformer)
- [Crate overview](#crate-overview)
- [Quick start](#quick-start)
- [Add the dependency](#1-add-the-dependency)
- [A minimal synchronous transformer](#2-a-minimal-synchronous-transformer)
- [Async example](#async-example)
- [Manual implementation (no macros)](#manual-implementation-no-macros)
- [3. (Optional) Build a transformer via a flow](#3-optional-build-a-transformer-via-a-flow)
- [Macro reference](#macro-reference)
- [Feature matrix](#feature-matrix)
- [Project status](#project-status)
- [License](#license)
- [Contributing](#contributing)
---
## Why use result‑transformer?
* **Decoupled by design** – success (`OkTransformer`) and error (`ErrTransformer`) paths are isolated, keeping concerns
clear and testable.
* **Symmetric sync / async** – every concept exists in twin flavours. You can reuse the same design, but switching to `async` requires the corresponding `_async` traits and macros.
* **Macro‑assisted ergonomics** – declarative `impl_*_transformer!` and `impl_*_transformer_via_*_flow!`
macros generate the boring glue while you focus on behaviour.
* **Optional “flow / step” DSL** – chain lightweight *steps* into a *flow* when you want a quick inline pipeline.
(It’s a helper, not a requirement.)
* **Tiny & feature‑gated** – pull in only the capabilities and dependencies you actually use.
---
## Crate overview
| **result-transformer** | facade crate reexporting the individual components | enable only the features you need |
| **result-transformer-core** | foundational traits and "raw" implementation macros | provides synchronous and asynchronous APIs |
| **result-transformer-flow** | *optional* step-based DSL used to compose transformers via macros | map/tap/inspect steps, optional logging |
| **result-transformer-macros** | procedural macros providing trait aliases and helpers | enabled via `*-macros` features |
| **result-transformer-dependencies** | consolidates external crates behind feature flags | internal helper crate |
| **result-transformer-test** | integration tests and doc examples that serve as real-world recipes | not intended for production |
`result-transformer-macros` is typically pulled in automatically when you enable one of the public `*-macros` features. You rarely need to depend on it directly.
---
## Quick start
### 1. Add the dependency
```toml
# Cargo.toml
result-transformer = { version = "0.0.2", features = ["core-sync", "core-sync-macros"] }
```
### 2. A minimal synchronous transformer
```rust
use result_transformer::sync::macros::*;
struct DoublePrefix;
/// doubles on success and prefixes errors – nothing more.
impl_ok_transformer! {
impl_for = DoublePrefix,
input_ok = i32,
output_ok = i32,
transform_ok = |v| v * 2
}
impl_err_transformer! {
impl_for = DoublePrefix,
input_err = &'static str,
output_err = String,
transform_err = |e| format!("E:{e}")
}
impl_result_transformer_via_self_parts! {
impl_for = DoublePrefix,
input_ok = i32,
input_err = &'static str
}
use result_transformer::sync::ResultTransformer; // needed to access the `transform` method
assert_eq!(DoublePrefix.transform(Ok(21)), Ok(42));
assert_eq!(DoublePrefix.transform(Err("no")), Err("E:no".into()));
```
To use these transformers in an asynchronous context, enable the `core-async` feature and import items from the `async_` module. The examples above will need to be rewritten with `async`/`await` and cannot be copied verbatim. The following snippet shows how to reuse the synchronous transformer by wrapping it with an asynchronous implementation.
#### Async example
```rust
use result_transformer::async_::macros::*;
impl_async_result_transformer_via_self_result_transformer! {
impl_for = DoublePrefix,
input_ok = i32,
input_err = &'static str
}
async {
assert_eq!(DoublePrefix.transform_async(Ok(21)).await, Ok(42));
assert_eq!(DoublePrefix.transform_async(Err("no")).await, Err("E:no".into()));
}
```
#### Manual implementation (no macros)
This approach is handy when macros are unavailable or you need finer-grained control.
```rust
use result_transformer::sync::{
OkTransformer, ErrTransformer, ResultTransformer
};
struct PlainTransformer;
impl OkTransformer<i32> for PlainTransformer {
type OutputOk = i32;
fn transform_ok(&self, ok: i32) -> Self::OutputOk {
ok * 2
}
}
impl ErrTransformer<&'static str> for PlainTransformer {
type OutputErr = String;
fn transform_err(&self, err: &'static str) -> Self::OutputErr {
format!("E:{err}")
}
}
impl ResultTransformer<i32, &'static str> for PlainTransformer {
type OutputOk = <Self as OkTransformer<i32>>::OutputOk;
type OutputErr = <Self as ErrTransformer<&'static str>>::OutputErr;
fn transform(&self,
result: Result<i32, &'static str>)
-> Result<Self::OutputOk, Self::OutputErr> {
match result {
Ok(v) => Ok(self.transform_ok(v)),
Err(e) => Err(self.transform_err(e)),
}
}
}
assert_eq!(PlainTransformer.transform(Ok(21)), Ok(42));
assert_eq!(PlainTransformer.transform(Err("no")), Err("E:no".into()));
```
The manual route makes the separation of concerns crystal‑clear – **each trait lives on its own** – and it requires
*zero* compiler magic. In larger codebases you’ll probably reach for the macros to avoid repetition, but both styles
interoperate seamlessly.
---
## 3. (Optional) Build a transformer *via* a flow
> **Note:** To use flows, enable the `flow-sync` and `flow-sync-macros` features.
When you do want a small pipeline the *flow / step* DSL has your back.
The example below chains two *steps* and then turns the resulting flow into a reusable transformer with
`impl_result_transformer_via_result_flow!`.
```rust
use result_transformer::flow::sync::{
step::map::ResultMapBothStep,
macros::impl_result_transformer_via_result_flow,
};
// any value implementing `ResultFlow` can be used – a single step is already a valid flow!
.then_result(ResultMapBothStep::new(|ok| ok + 1, |err| format!("E:{err}")));
struct ViaFlow;
impl_result_transformer_via_result_flow! {
impl_for = ViaFlow,
input_ok = i32,
input_err = &'static str,
output_ok = i32,
output_err = String,
flow = flow
}
```
---
## Macro reference
The crates expose a variety of helper macros. Each synchronous macro has an
asynchronous counterpart prefixed with `async_`.
### Implementation macros
- `impl_ok_transformer!` / `impl_async_ok_transformer!` – implement
`OkTransformer` or `AsyncOkTransformer` using a custom function.
- `impl_err_transformer!` / `impl_async_err_transformer!` – implement
`ErrTransformer` or `AsyncErrTransformer` using a custom function.
- `impl_result_transformer!` / `impl_async_result_transformer!` – provide a
function transforming the entire `Result`.
- `impl_ok_transformer_via_input_into!`, `impl_ok_transformer_via_output_from!`
and their `err` equivalents – derive implementations via `Into`/`From`.
- `impl_result_transformer_via_ok_transform_fn!` and
`impl_result_transformer_via_err_transform_fn!` – combine existing
single-sided transforms.
- `impl_result_transformer_via_self_parts!` – reuse `OkTransformer` and
`ErrTransformer` implementations from the same type.
### Flow macros
- `impl_result_transformer_via_result_flow!` – build a transformer from a flow.
- `impl_ok_transformer_via_ok_flow!` and `impl_err_transformer_via_err_flow!` –
the same idea for single-sided traits.
- `chain_result_flow!`, `chain_ok_flow!`, `chain_err_flow!` – chain flows at
compile time.
- `define_const_*_step!` – create reusable const steps for the flow DSL.
### Alias macros
Procedural macros generate trait aliases:
- `alias_ok_transformer!`, `alias_err_transformer!`, `alias_result_transformer!`
- `alias_async_ok_transformer!`, `alias_async_err_transformer!`,
`alias_async_result_transformer!`
---
## Feature matrix
| `default` | enables `core-sync` |
| `core-sync` | enables the synchronous core API |
| `core-sync-macros` | helper macros for the synchronous model |
| `core-sync-all` | `core-sync` + `core-sync-macros` |
| `core-async` | enables the asynchronous core API |
| `core-async-macros` | helper macros for the asynchronous model |
| `core-async-all` | `core-async` + `core-async-macros` |
| `core-all` | all features from `result-transformer-core` |
| `flow-sync` | enables the synchronous *flow / step* DSL |
| `flow-sync-macros` | helper macros for synchronous flows |
| `flow-sync-log-step` | adds a logging step for synchronous flows |
| `flow-sync-all` | `flow-sync` + `flow-sync-log-step` + `flow-sync-macros` |
| `flow-async` | enables the asynchronous *flow / step* DSL |
| `flow-async-macros` | helper macros for asynchronous flows |
| `flow-async-log-step` | adds a logging step for asynchronous flows |
| `flow-async-all` | `flow-async` + `flow-async-log-step` + `flow-async-macros` |
| `flow-all` | all features from `result-transformer-flow` |
| `sync-all` | `core-sync-all` + `flow-sync-all` |
| `async-all` | `core-async-all` + `flow-async-all` |
| `all` | every feature in this crate |
Pick exactly what you need and keep compile times down.
Only `core-sync` is enabled by default. Combine features as needed.
> Some features automatically enable their dependencies.
> For example, `flow-async-macros` will automatically enable `flow-async` and `core-async`.
> You can still list them explicitly if you want to be more precise.
Example: enabling the async execution model, flow DSL, and flow macros:
```toml
result-transformer = { version = "0.0.2", features = ["flow-async-macros"] }
```
Or, if you prefer to list everything explicitly:
```toml
result-transformer = { version = "0.0.2", features = ["core-async", "flow-async", "flow-async-macros"] }
```
---
## Project status
This project is currently in early development (`0.0.2`) and **not yet stable**.
APIs — especially in the `flow` crate — are subject to change.
---
## License
Licensed under either of
* **MIT** license *(LICENSE-MIT)*
* **Apache‑2.0** license *(LICENSE-APACHE)*
at your option.
---
## Contributing
Bug reports, feature requests, and pull requests are always welcome.
> I'm still new to GitHub and not very confident in English.
> For now, I'm doing my best with the help of ChatGPT,
> so I might misunderstand something. Thanks for your understanding!
---