kaiv for Avro Users — A Cookbook

for kaiv 0.4.0

1 Introduction

You use Avro — records with schemas, logical types, compact binary files flowing through Kafka topics and data lakes. This cookbook maps Avro onto kaiv, which shares Avro’s core belief — data travels with its contract — and inverts the economics: where an Avro datum is unreadable without its schema, every kaiv canonical line is self-describing text, and where Avro Schema stops at structure, kaiv constrains values — ranges, patterns, enums, lengths.

Conversion runs both ways: your .avsc converts to a kaiv schema, your data moves between Avro object container files and canonical text losslessly, and the pipeline keeps its binary files where binary earns its keep.

Every example is a verified transcript of the reference kaiv tool (cargo install kaiv-cli), run against the fixture files shipped beside this document.

2 Your Schema Converts

A record with the shapes Avro pipelines lean on — a logical type, the ["null", ...] optionality idiom, a decimal:

{
  "type": "record",
  "name": "Reading",
  "fields": [
    {"name": "sensor", "type": "string"},
    {"name": "celsius", "type": "double"},
    {"name": "taken", "type": {"type": "long", "logicalType": "timestamp-millis"}},
    {"name": "note", "type": ["null", "string"], "default": null},
    {"name": "price", "type": {"type": "bytes", "logicalType": "decimal", "precision": 9, "scale": 2}}
  ]
}
$ kaiv import-schema --avro --name acme/reading reading.avsc
.!kaivschema 1 acme/reading
.!types std/num
.!types std/time

sensor=
!float|std/num/inf|std/num/nan
celsius=
&datetime
taken=
!null|str
note=
!float
price=

Reading it back: timestamp-millis became &datetime from std/time — a named type, not a bare long, so the value reads as a timestamp and validates as one. The ["null","string"] idiom became the tagged union !null|str — the same idea, one token. And double became the extended-reals union: the Avro spec admits NaN and the infinities in a double, so a faithful conversion must too — std/num names them instead of pretending they cannot arrive.

3 Records In, Records Out

kaiv writes and reads Avro object container files — the self-contained kind, schema embedded in the header:

$ printf '{"sensor":"t-01","celsius":21.5,"taken":"2026-07-17T09:30:00Z","note":null,"price":"19.99"}' | kaiv import --json | kaiv build > reading.daiv
$ kaiv export --avro reading.daiv | wc -c
317
$ kaiv export --avro reading.daiv | kaiv import --avro | kaiv build
.!kaiv 1
!str'::sensor=t-01
!float'::celsius=21.5
!str'::taken=2026-07-17T09:30:00Z
!null'::note=
!str'::price=19.99

Three hundred seventeen bytes of container — header, embedded schema, datum — and every value returns intact, the null included as an explicit !null line. The active-variant rule governs unions in canonical form: the data line’s type annotation says which alternative this value took, so note is !null when absent and !str when present — Avro’s union branch index, readable.

4 Validation with Teeth

The converted schema validates canonical documents directly — and because taken is &datetime, not a bare long, malformed timestamps have nowhere to hide:

$ kaiv import-schema --avro --name acme/reading reading.avsc > reading.saiv
$ kaiv validate reading.daiv reading.saiv
pass
$ printf '{"sensor":"t-01","celsius":21.5,"taken":"yesterday","note":null,"price":"19.99"}' | kaiv import --json | kaiv build > bad-taken.daiv
$ kaiv validate bad-taken.daiv reading.saiv
kaiv: ConstraintViolationError: ::taken=yesterday (type !str) violates /^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/ ..time (line 4)

The error carries std/time/datetime’s full compiled pattern and temporal span — field, value, constraint, line. Exit code 1; pass and exit 0 otherwise. From here the schema is a kaiv schema: tighten it with ranges and patterns Avro Schema has no syntax for.

5 The Mapping

Avro kaiv
record namespace / document ordered in both
["null", T] !null|T union the active variant annotates
enum enum constraint {a,b,c}
map !map<T>
array @-array
fixed / bytes !b64 / &bin base64url
logical types std/time named types &datetime, &date, &uuid
decimal !float exact decimals on export
double extended-reals union NaN/Inf admitted, as in Avro
aliases mapping documents a graph edge, not an attribute
doc // comments

6 When to Stay with Avro

An honest map marks the roads not taken. Avro remains the right choice when:

kaiv’s pull is at the edges of the pipeline: the sample a human inspects, the fixture a test pins, the record an audit must keep readable for a decade, the contract that needs a range and not just a type — with the container round trip above as the bridge, both directions, byte-faithful.

7 Where Next

From here: