Skip to main content

Crate difa

Crate difa 

Source
Expand description

§ADIF Parsing for Rust

CI codecov

§Overview

This crate parses Amateur Data Interchange Format (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.

§Usage

Add the dependency:

cargo add difa

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

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 format. Reading Cabrillo format is not supported.

§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 4500 code regions. The nightly toolchain may be used to verify branch coverage.

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

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.

Re-exports§

pub use cabrillo::CabrilloSink;
pub use filter::FilterExt;
pub use filter::NormalizeExt;
pub use parse::RecordStream;
pub use parse::RecordStreamExt;
pub use parse::TagDecoder;
pub use parse::TagStream;
pub use write::OutputTypes;
pub use write::RecordSink;
pub use write::TagEncoder;
pub use write::TagSink;
pub use write::TagSinkExt;

Modules§

cabrillo
Writing Cabrillo contest log format
filter
Optional ADIF data transformations
parse
Parsing of ADIF data at various levels of sophistication
write
Writing ADIF data to async writers

Structs§

CiStr
A case-insensitive borrowed string slice.
CiString
A case-insensitive string that preserves the original case.
Field
A single tag in an ADIF stream and its associated value
Position
Position information for errors in the input stream.
Record
A single contact record, composed of multiple data fields

Enums§

Datum
Value for a field in an ADIF record.
Error
Errors that can occur during ADIF parsing and processing.
Tag
A single tag and following value within an ADIF stream