README.md

# QuiqupAvroEx

[![Hex.pm](https://img.shields.io/hexpm/v/quiqup_avro_ex.svg)](https://hex.pm/packages/quiqup_avro_ex)
[![CI](https://github.com/quiqupltd/quiqup_avro_ex/actions/workflows/ci.yml/badge.svg)](https://github.com/quiqupltd/quiqup_avro_ex/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/badge/coverage-80%25-brightgreen.svg)](https://github.com/quiqupltd/quiqup_avro_ex)

Avro encoder/decoder with Confluent Schema Registry support using [AvroEx](https://github.com/beam-community/avro_ex).

This library provides a simple API for encoding and decoding Avro messages using the Confluent wire format, with built-in schema registry integration and caching.

## Why QuiqupAvroEx?

We built this as a replacement for [Avrora](https://github.com/Strech/avrora) after encountering schema parsing issues with erlavro (the Erlang Avro library). AvroEx is a pure Elixir implementation that handles complex schemas more reliably.

## Installation

Add `quiqup_avro_ex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:quiqup_avro_ex, "~> 0.1.0"}
  ]
end
```

## Configuration

```elixir
config :quiqup_avro_ex,
  registry_url: "http://localhost:8081",
  schemas_path: "./priv/schemas",
  cache_ttl: :timer.minutes(5)
```

Or use environment variables:
- `KAFKA_SCHEMA_REGISTRY` - Schema registry URL

## Usage

### Decoding Messages

Decode Confluent wire format messages (magic byte + schema ID + payload):

```elixir
# Decode a Kafka message
{:ok, data} = QuiqupAvroEx.decode(message)
```

### Encoding Messages

Encode data using a schema name or ID:

```elixir
# Using schema name (looks up from registry or local files)
{:ok, encoded} = QuiqupAvroEx.encode(data, schema_name: "my.namespace.MyRecord")

# Using schema ID directly
{:ok, encoded} = QuiqupAvroEx.encode(data, schema_id: 123)
```

### Schema Registration

Register schemas with the Schema Registry:

```elixir
# Register from local file (schema_name maps to file path)
{:ok, schema_id} = QuiqupAvroEx.register_schema("my.namespace.MyRecord")

# Register with explicit schema JSON
schema_json = ~s({"type": "record", "name": "MyRecord", ...})
{:ok, schema_id} = QuiqupAvroEx.register_schema("my.namespace.MyRecord", schema_json)
```

### Schema Management

```elixir
# Get schema by ID
{:ok, schema} = QuiqupAvroEx.get_schema(123)

# Get schema by name (returns {schema, schema_id})
{:ok, {schema, schema_id}} = QuiqupAvroEx.get_schema_by_name("my.namespace.MyRecord")

# List all schema versions
{:ok, versions} = QuiqupAvroEx.get_schema_versions("my.namespace.MyRecord")

# Check compatibility
{:ok, true} = QuiqupAvroEx.check_compatibility("my.namespace.MyRecord", new_schema_json)

# Delete a schema
:ok = QuiqupAvroEx.delete_schema("my.namespace.MyRecord")

# Clear the cache
:ok = QuiqupAvroEx.clear_cache()
```

### Wire Format Utilities

```elixir
# Parse wire format header
{:ok, schema_id, payload} = QuiqupAvroEx.parse_wire_format(message)

# Build wire format message
message = QuiqupAvroEx.build_wire_format(schema_id, encoded_payload)
```

## Local Schema Files

Schemas can be loaded from local files as a fallback when the registry is unavailable. The file path is derived from the schema name:

- `"my.namespace.MyRecord"` → `./priv/schemas/my/namespace/MyRecord.avsc`
- `"Couriers.CourierEvents.OrderAdded"` → `./priv/schemas/Couriers/CourierEvents/OrderAdded.avsc`

## Migration from Avrora

QuiqupAvroEx provides API compatibility with Avrora:

```elixir
# Before (Avrora)
{:ok, data} = Avrora.decode(message)
{:ok, encoded} = Avrora.encode(data, schema_name: "my.schema")

# After (QuiqupAvroEx)
{:ok, data} = QuiqupAvroEx.decode(message)
{:ok, encoded} = QuiqupAvroEx.encode(data, schema_name: "my.schema")
```

## License

MIT