# ADIF Parsing for Rust

[![CI](https://github.com/cammeresi/adif/workflows/Rust/badge.svg)](https://github.com/cammeresi/adif/actions)
[![codecov](https://codecov.io/gh/cammeresi/adif/branch/master/graph/badge.svg)](https://codecov.io/gh/cammeresi/adif)

## Overview

This crate parses [Amateur Data Interchange Format][adif] (ADIF) data using
asynchronous streams.

ADIF is a standard data format used by ham radio operators to exchange
information logged about past contacts.  This crate provides a few ways to
parse ADIF data in Rust on top of tokio.

If DIFA needs to stand for something, it stands for Data Interchange Format for
Amateurs.

[adif]: https://adif.org/

## Usage

Add the dependency:

```sh
cargo add difa
```

Then start reading ADIF from any object that implements the AsyncRead trait:

```rust
use difa::RecordStream;
use futures::StreamExt;
use tokio::{fs::File, io::BufReader};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("examples/sample.adif").await?;
    let reader = BufReader::new(file);
    let mut stream = RecordStream::new(reader, true);
    while let Some(result) = stream.next().await {
        let record = result?;
        if let Some(call) = record.get("call") {
            println!("call: {}", call.as_str());
        }
    }
    Ok(())
}
```
See examples or documentation for further information.

## Features

Because there is some variety to the ADIF fields generated by various
programs, the data reader usually needs to engage in some interpretation
of the data based on its source.

This parser is intended to be maximally flexible and provide the user
with parsed ADIF data from any source at any level with or without
preprocessing.

Input is read from a stream.  File input is not assumed.  Reading an
entire file is not assumed; it's fine to start in the middle.  Trailing
data in the form of a partial tag or record can be ignored or return an
error.  Leading text is ignored.

The code in this crate strives to be panic-free, extremely safe, and
lightweight in terms of memory usage.  (There is a single line of unsafe
code in [`CiStr::new`] that transmutes references from a `&str` to a
transparent wrapper type.)

## Components

The [TagStream] provides the lowest level of output:  individual ADIF
tags and their associated values.  Values are parsed and are strongly
typed, although in the absence of type specifiers (which is common),
data can be coerced to the desired type when accessed.

The [RecordStream] provides higher level output by aggregating fields
into records, each representing one contact.  Records may then be indexed
into by key.

A number of data normalizers are provided in the [filter] module that
can be stacked on top of a [RecordStream] to automatically transform
records as they are read.  They are intended to be generally useful at
smoothing some of ADIF's roughest edges, but they are not necessarily
every single transformation an application might desire.  The user can,
however, write additional normalizers to implement additional
transformations not heretofore envisioned by the author.

As a convenience tool, the crate also contains a [CabrilloSink] to output
records as a contest log in [Cabrillo][cabrillo] format.  Reading Cabrillo
format is not supported.

[cabrillo]: https://wwrof.org/cabrillo/cabrillo-v3-header/

## Testing

Test coverage of this crate is 100% as of 2025-11-27.

Every single function, every single line, every single expression,
and every single character in the entire crate is executed by at least
one test.

Every single branch in every single function is tested by at least one
concrete instantiation.  (For function `Foo<T>::bar`, there exists at
least one `T` for which the tests test every possible path through `bar`.)

These facts are verified by both `cargo llvm-cov` and codecov.io (badge
at top) across over 5000 code regions.  The nightly toolchain may be
used to verify branch coverage.

[[Mutation testing][mutation]] via `cargo mutants` shows that any change
to the code causes a test failure, i.e. any change of code causes a
change in the output.

> I reviewed your flight plan.  Not one error in a million keystrokes.
> Phenomenal.  [[Gattaca, 1997][gattaca]]

[mutation]: https://en.wikipedia.org/wiki/Mutation_testing
[gattaca]: https://en.wikipedia.org/wiki/Gattaca

Additionally, each of the property-based tests has run over one million
times without error.

Although performance is not a primary concern, some benchmarking of
this crate has been performed to measure parsing performance on a single
core using synthetic data with 13 fields per record.

- Apple M3 Pro processor (2023) — 544,000 contacts per second
- Intel Core i5 processor (2021) — 252,000 contacts per second

## Author

Sidney Cammeresi, AB9BH

THE AUTHOR HAS MADE AND MAKES NO REPRESENTATION OR WARRANTY WHATSOEVER,
EITHER EXPRESS OR IMPLIED, THAT THIS CRATE IS NOT SIGNIFICANTLY AND
RIDICULOUSLY OVER-ENGINEERED.

