dev.constructive.eo.avro

Cross-representation optics bridging native Scala types and their Apache Avro on-the-wire form.

The entry point is AvroPrism.codecPrism (read-aloud API parallel to dev.constructive.eo.circe.codecPrism):

 import dev.constructive.eo.avro.{AvroCodec, codecPrism}
 import hearth.kindlings.avroderivation.AvroEncoder.derived
 import hearth.kindlings.avroderivation.AvroDecoder.derived
 import hearth.kindlings.avroderivation.AvroSchemaFor.derived

 case class Person(name: String, age: Int)
 // `given AvroCodec[Person]` falls out automatically from kindlings'
 // derived `AvroEncoder[Person]`, `AvroDecoder[Person]`, `AvroSchemaFor[Person]`.

 val personPrism: AvroPrism[Person]   = codecPrism[Person]
 val namePrism:   AvroPrism[String]   = personPrism.field(_.name)
 namePrism.modify(_.toUpperCase)(payloadBytes)
 // → the same Array[Byte] payload, with `name` upper-cased in
 //   place — no Person (nor any root record) ever materialised.
 namePrism.record.modify(_.toUpperCase)(record)
 // → the IndexedRecord-carried face, with Ior diagnostics.

The default carrier is the binary wire form itself (Array[Byte], mirroring dev.constructive.eo.jsoniter.JsoniterPrism); the record-carried face behind .record follows dev.constructive.eo.circe's architecture decisions on Json: IndexedRecord plays the role of Json, AvroCodec plays the role of (io.circe.Encoder[A], io.circe.Decoder[A]), and AvroFailure plays the role of JsonFailure.

'''Carrier note.''' Both AvroRecordPrism and eo-circe's JsonPrism are Affine-carried — lawful Optionals whose composed / upcast writes preserve siblings. A drilled focus is an Optional, so the carrier widens to Affine (via the Composer on composition) rather than pretending to be a Prism; the two record faces stay in step.

'''Deliberate duplication.''' PathStep is duplicated, not shared with eo-circe — the UnionBranch case is Avro-only and forcing it into eo-circe would pollute that module. See PathStep's class doc.

Attributes

Members list

Packages

Structural Avro ↔ circe bridge (AvroJson): move between Avro generic runtime values, payload bytes and io.circe.Json with no typed case class in the middle. circe is an '''Optional''' dependency of cats-eo-avro — add circe-core to use this package.

Structural Avro ↔ circe bridge (AvroJson): move between Avro generic runtime values, payload bytes and io.circe.Json with no typed case class in the middle. circe is an '''Optional''' dependency of cats-eo-avro — add circe-core to use this package.

Attributes

Type members

Classlikes

object AvroBridge

Constructor for AvroBridge.

Constructor for AvroBridge.

Attributes

Companion
class
Source
AvroBridge.scala
Supertypes
class Object
trait Matchable
class Any
Self type
AvroBridge.type
final class AvroBridge[A, B] extends Optic[Array[Byte], BridgedBytes, A, B, Affine]

A two-version Avro migration bridge as an Affine-carried optic. Named from the OPTIC's point of view — it reads with the readCodec and writes with the writeCodec:

A two-version Avro migration bridge as an Affine-carried optic. Named from the OPTIC's point of view — it reads with the readCodec and writes with the writeCodec:

 AvroBridge.between[A, B]:
   Optic[Array[Byte],   // bytes READ (encoded under readCodec's schema)
         BridgedBytes,  // = Either[AvroFailure, Array[Byte]] — bytes WRITTEN (under writeCodec's schema), or the failure
         A,             // readFocus:  the value read out of the source bytes
         B,             // writeFocus: the value written back
         Affine]

to decodes the source bytes under the readCodec's schema — a Miss (so getOption is None) when they don't decode as an A. You supply the A ⇒ B migration as the function passed to the carrier-generic .modify (import dev.constructive.eo.optics.Optic.*); from re-encodes the resulting B under the writeCodec's schema. That re-encode can itself fail, and eo has no carrier whose from is fallible — so the outcome lives in the target type T = BridgedBytes = Either[AvroFailure, Array[Byte]] (this is the "BiAffine behaviour without a new carrier" we settled on; the generalized optic that would carry a fallible build natively is deferred).

'''Directed by design.''' The read side is A (the readCodec), the write side B (the writeCodec). AvroBridge.reverse swaps the two codecs to give the Optic[Array[Byte], BridgedBytes, B, A, Affine] going the other way. A fully symmetric encoding would be a BijectionIso Optic[BridgedBytes, BridgedBytes, A, B, Direct], but we deliberately favour directedness over that Iso and don't implement it.

Each side decodes / encodes under its OWN exact schema, so this is an explicit, user-driven migration between two model versions — NOT Avro's automatic writer→reader compatibility resolution (ResolvingDecoder; for that, see ConfluentWire.reader). The decode / encode go through the shared AvroCodec.decodeValue / AvroCodec.encodeValue helpers — no serialization path of its own.

Being an Optic[…, Affine], it picks up the capability-gated surface — .getOption, .modify, .replace, .andThen, … — from dev.constructive.eo.optics.Optic.

Attributes

Companion
object
Source
AvroBridge.scala
Supertypes
trait Optic[Array[Byte], BridgedBytes, A, B, Affine]
class Object
trait Matchable
class Any
object AvroCodec

Attributes

Companion
trait
Source
AvroCodec.scala
Supertypes
class Object
trait Matchable
class Any
Self type
AvroCodec.type
trait AvroCodec[A]

A unified read/write/schema codec for Avro values, combining the kindlings-avro-derivation triplet (AvroEncoder[A], AvroDecoder[A], AvroSchemaFor[A]) into a single typeclass that user code summons once per type.

A unified read/write/schema codec for Avro values, combining the kindlings-avro-derivation triplet (AvroEncoder[A], AvroDecoder[A], AvroSchemaFor[A]) into a single typeclass that user code summons once per type.

Why an extra wrapper at all? kindlings ships three independent typeclasses so users can encode- only or decode-only — but cats-eo-avro's optics always need both sides to be honest (a Prism's get decodes, its reverseGet / place encodes). Forcing every call site to thread two using parameters is noisy. AvroCodec[A] is the project-internal shorthand.

Why not vulcan? Vulcan 1.13.x pins apache-avro 1.11.5; kindlings-avro-derivation 0.1.2 pins 1.12.1. cats-eo-avro chose kindlings + avro 1.12 because it lines up with the rest of the ecosystem moving forward, and the typeclass surface is simpler (no Either[AvroError, A] threading on every call — kindlings' decoders throw on failure, which the prism layer wraps into AvroFailure).

'''Per the eo-avro plan (OQ-avro-1):''' the codec library at v0.1.0 is com.kubuszok:kindlings-avro-derivation. Vulcan and raw apache-avro stay viable as future alternatives if user demand surfaces, behind a copy-paste of this file.

Attributes

Companion
object
Source
AvroCodec.scala
Supertypes
class Object
trait Matchable
class Any

Structured failure surfaced by the default Ior-bearing surface of AvroPrism.

Structured failure surfaced by the default Ior-bearing surface of AvroPrism.

Every case carries a PathStep so the walk that produced the failure can point at the specific cursor position that refused. The default enum toString keeps the structural representation for testability; message gives a human-readable diagnostic.

Mirrors dev.constructive.eo.circe.JsonFailure case-for-case, plus the schema-driven cases that Avro adds: AvroFailure.UnionResolutionFailed, AvroFailure.BadEnumSymbol. The two tied carriers differ on exactly the schema-aware concept set; like PathStep, this ADT is a deliberate divergence from JsonFailure — sharing would force Avro-specific cases into eo-circe.

Attributes

Companion
object
Source
AvroFailure.scala
Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object AvroFailure

Attributes

Companion
enum
Source
AvroFailure.scala
Supertypes
trait Sum
trait Mirror
class Object
trait Matchable
class Any
Self type
final class AvroFailureException(val failure: AvroFailure) extends RuntimeException

Carries an AvroFailure as a Throwable, so an effectful reader (e.g. ConfluentWire.reader under a MonadThrow[F]) can raiseError the structured failure into F's error channel. The pure surface keeps returning Either[AvroFailure, …]; this is only the bridge to an F that fails.

Carries an AvroFailure as a Throwable, so an effectful reader (e.g. ConfluentWire.reader under a MonadThrow[F]) can raiseError the structured failure into F's error channel. The pure surface keeps returning Either[AvroFailure, …]; this is only the bridge to an F that fails.

Attributes

Source
AvroFailure.scala
Supertypes
class RuntimeException
class Exception
class Throwable
trait Serializable
class Object
trait Matchable
class Any
Show all
final case class AvroFragment(bytes: Array[Byte], schema: Schema, branchOrdinal: Option[Int])

An encoded byte fragment sliced out of a binary Avro payload by AvroPrism.sliceBytes — the value bytes of one focused field, ready to be hashed, shipped around, or grafted into another payload via AvroPrism.graftBytes.

An encoded byte fragment sliced out of a binary Avro payload by AvroPrism.sliceBytes — the value bytes of one focused field, ready to be hashed, shipped around, or grafted into another payload via AvroPrism.graftBytes.

When the slicing prism focused a union branch (.union[Branch]), the branch-index bytes are STRIPPED: bytes carries the branch value only, schema is the branch schema, and branchOrdinal reports the branch's ordinal in the union (graft re-synthesises the index from the receiving prism's own path, so the ordinal here is informational — e.g. for schema-fingerprint bookkeeping). For non-union focuses branchOrdinal is None and bytes is the field's full encoding.

Value parameters

branchOrdinal

the union branch ordinal when the focus was a union branch; None otherwise

bytes

the encoded value bytes (union branch index stripped)

schema

the schema the bytes are encoded under (the resolved field / branch schema)

Attributes

Source
AvroFragment.scala
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
final class AvroPrism[A] extends Optic[Array[Byte], Array[Byte], A, A, Affine], Dynamic

Optic from the Avro BINARY WIRE FORM to a native type A — the wire bytes are the default carrier:

Optic from the Avro BINARY WIRE FORM to a native type A — the wire bytes are the default carrier:

 AvroPrism[A] <: Optic[Array[Byte], Array[Byte], A, A, Affine]
 type X = (Array[Byte], (Array[Byte], BinarySpan))

Reads locate the focused field's byte span via AvroBinaryCursor and decode only that slice; writes re-encode the focus and splice it in place (three arraycopys, union branch index re-synthesised). The ROOT object is never materialised — neither as a case class nor as a generic record; a record-SHAPED focus is materialised only as that branch's own org.apache.avro.generic.IndexedRecord during the slice decode / re-encode. Mirrors dev.constructive.eo.jsoniter.JsoniterPrism shape-for-shape:

  • Fst[X] = Array[Byte] — original payload; Miss carries it for pass-through (parse failure, path miss, union-branch mismatch, decode failure, unsupported index step).
  • Snd[X] = (Array[Byte], BinarySpan) — payload + located span, so from can splice.

The whole capability-gated extension surface lights up on the bytes — .getOption, .modify, .replace, .foldMap, .andThen, … Drill with the same macro sugar as ever (.field(_.x) / .fields(...) / .union[B] / .at(i) / .each / Dynamic field selection).

'''Field navigation honours the SCHEMA field name (issue #35).''' .field(_.x) (and .fields, selectDynamic, the traversal siblings) resolve the case-class field x to whatever schema field the codec actually emitted for it — under any name transform (kindlings snake / kebab / custom transformFieldNames, or a vulcan per-field override map) — by DECLARATION POSITION: the i-th case field maps to the i-th schema field, read back out of the cached schema at construction time (zero per-operation cost). The rare hand-written codec whose schema field ORDER diverges from declaration order needs AvroPrism.fieldNamed("schema_name") to navigate by the explicit schema name instead. Map keys are data, not schema-named fields, and keep their literal key.

Two sibling surfaces, one mechanism each (deliberately NOT duplicated here):

  • record — the IndexedRecord-carried optic (AvroRecordPrism) with the Ior-bearing diagnostic surface (get / modify / place / transfer + *Unsafe) over IndexedRecord | Array[Byte] | String input. Use it when you hold parsed records or need accumulated AvroFailure diagnostics.
  • sliceBytes / graftBytes — the encoded-fragment surface for hashing / shipping / splicing a field's raw encoding across payloads without decoding the focus at all.

Storage decomposition: an AvroPrism[A] holds an AvroFocus (Leaf vs Fields) and a cached root schema; the byte walk uses the focus's path, the slice decode uses its codec.

'''Laws & preconditions''' (normative):

  • The Optional laws hold '''up to canonical re-encoding of the focused slice''': modify(identity) re-encodes the focus, so a payload whose focused slice used non-canonical (but spec-legal) encodings — non-minimal varints, byte-sized array blocks — comes back canonicalised. Byte-for-byte identity holds for payloads from conformant writers (apache-avro's own encoders included); put-get and put-put hold unconditionally.
  • '''Writes require a decodable current focus''': the Affine to decodes eagerly, so .replace onto a span whose current value doesn't decode as A — or a .union[B] focus sitting on a different runtime branch — is a Miss pass-through. graftBytes is the decode-free write (and the only one that can SWITCH union branches).
  • '''The payload must be encoded under exactly this prism's reader schema.''' The byte walk performs no writer/reader schema resolution: structurally drifted payloads Miss silently, and a same-typed field REORDER between writer and reader is undetectable from the bytes — the walk reads the wrong field with full confidence. Confluent-framed payloads are handled by composing ConfluentWire.confluent (a byte Prism that strips the header, resolves the writer schema, and fingerprint-gates) BEFORE this optic — confluent.andThen(thisWalk); past a fingerprint mismatch a mixed-schema topic still needs a resolving decode (the record face with the right schema per payload).
  • Dynamic field sugar is shadowed by real members: an Avro field named like a member of this class (record, field, at, union, each, fields, …) must be drilled with the explicit .field(_.record) form.

Attributes

Companion
object
Source
AvroPrism.scala
Supertypes
trait Dynamic
trait Optic[Array[Byte], Array[Byte], A, A, Affine]
class Object
trait Matchable
class Any
object AvroPrism

Attributes

Companion
class
Source
AvroPrism.scala
Supertypes
class Object
trait Matchable
class Any
Self type
AvroPrism.type

Macros backing the drilling sugar on AvroPrism and AvroTraversal.field(_.x), selectDynamic, .at(i), .union[Branch], .each, .fields(...). Mirror of circe.JsonPrismMacro, with two-given codec summoning (AvroEncoder + AvroDecoder) collapsed to one AvroCodec wrapper. Uses '{ this } rather than 'this (scalafix-parser limitation).

Macros backing the drilling sugar on AvroPrism and AvroTraversal.field(_.x), selectDynamic, .at(i), .union[Branch], .each, .fields(...). Mirror of circe.JsonPrismMacro, with two-given codec summoning (AvroEncoder + AvroDecoder) collapsed to one AvroCodec wrapper. Uses '{ this } rather than 'this (scalafix-parser limitation).

Attributes

Source
AvroPrismMacro.scala
Supertypes
class Object
trait Matchable
class Any
Self type
final class AvroRecordPrism[A] extends Optic[IndexedRecord, IndexedRecord, A, A, Affine]

The org.apache.avro.generic.IndexedRecord-carried face of an AvroPrism — reachable ONLY through AvroPrism.record, never constructed directly:

The org.apache.avro.generic.IndexedRecord-carried face of an AvroPrism — reachable ONLY through AvroPrism.record, never constructed directly:

 AvroRecordPrism[A]  IndexedRecord)  // Fst = source (miss); Snd = single-walk writer

'''An Optional, not a Prism.''' A drilled record focus lives INSIDE a record, so rebuilding the record needs the siblings — which a Prism's from(reverseGet) cannot see (it gets the focus alone). Carrying the source on the Affine seam fixes that at the carrier level: to captures a writer over the walk it already did, and from(Hit) applies it — so .modify / .replace preserve siblings whether called directly, upcast to Optic[…, Affine], or composed via andThen (the composite threads the writer end-to-end). Same carrier and law story as the byte face; only the root full-cover prism is also a lawful Prism, and reverseGet stays for it.

Two call-surface tiers (via AvroOpticOps):

  • Default Ior-bearing: modify / get etc. accumulate Chain[AvroFailure] on failure; partial success surfaces as Ior.Both(chain, inputRecord).
  • *Unsafe: silent pass-through hot path.

Triple-input shape IndexedRecord | Array[Byte] | String (where String is the Avro JSON wire format), decoding byte / JSON input under the cached root schema.

Drilling (.field / .fields / .union / .at / .each / Dynamic selection) lives on AvroPrism alone — drill there, then flip with .record at the end. One drilling mechanism, two carriers.

Attributes

Source
AvroRecordPrism.scala
Supertypes
trait Optic[IndexedRecord, IndexedRecord, A, A, Affine]
class Object
trait Matchable
class Any
final class AvroRecordTraversal[A]

The org.apache.avro.generic.IndexedRecord-carried face of an AvroTraversal — reachable ONLY through AvroTraversal.record, never constructed directly. Walks the record from the root down to some array, then applies the focus update to every element of that array.

The org.apache.avro.generic.IndexedRecord-carried face of an AvroTraversal — reachable ONLY through AvroTraversal.record, never constructed directly. Walks the record from the root down to some array, then applies the focus update to every element of that array.

Mirrors dev.constructive.eo.circe.JsonTraversal line-for-line: a traversal is "a prism applied per-element after a prefix walk". Two call-surface tiers (via AvroOpticOps):

  • '''Default (Ior-bearing).''' modify / transform / place / transfer / getAll accumulate per-element failures into the chain. Prefix-walk failures return Ior.Left — nothing to iterate.
  • '''*Unsafe (silent).''' Drops failures; preserves the silent forgiving semantics.

Drilling (.field / .at / .fields / Dynamic selection) lives on AvroTraversal alone — drill there, then flip with .record at the end. One drilling mechanism, two carriers.

Attributes

Source
AvroRecordTraversal.scala
Supertypes
class Object
trait Matchable
class Any
object AvroTraversal

Attributes

Companion
class
Source
AvroTraversal.scala
Supertypes
class Object
trait Matchable
class Any
Self type
final class AvroTraversal[A] extends Optic[Array[Byte], Array[Byte], A, A, MultiFocus[PSVec]], Dynamic

Multi-focus counterpart to AvroPrism, carried on the BINARY WIRE FORM like the prism:

Multi-focus counterpart to AvroPrism, carried on the BINARY WIRE FORM like the prism:

 AvroTraversal[A] <: Optic[Array[Byte], Array[Byte], A, A, MultiFocus[PSVec]]
 type X = (Array[Byte], Option[ElementSpans])

to resolves the prefix to an array-typed field, walks the array's block framing, and locates + slice-decodes the focus inside every element (AvroBinaryCursor.locateElements); from re-encodes each focus and splices all spans back in one pass. Avro's standard (positive-count) array framing carries no byte lengths, so per-element splices can change size freely; payloads whose array used the negative-count (byte-sized) framing are RE-FRAMED on write — the whole array region is rebuilt as one canonical positive-count block with the focus splices applied (AvroBinaryCursor.reframeArray). Same carrier as dev.constructive.eo.jsoniter.JsoniterTraversal (MultiFocus[PSVec]), so .foldMap / .all / .modify / .headOption / same-carrier .andThen light up from the standard extension catalogue.

Read semantics are "fold the focuses that exist": elements whose focus walk or slice decode refuses keep their bytes but leave the focus set (untouched on write). Whole-walk failure (bad prefix / non-array terminal / truncated bytes) yields no foci and from returns the input — silent, like every write-side refusal here (diagnostics live on record).

AvroPrism's '''Laws & preconditions''' section applies verbatim: laws hold up to canonical re-encoding of the focused slices, writes need decodable current focuses, and the payload must be encoded under exactly this traversal's reader schema.

The record-carried face — the Ior-bearing modify / getAll / place / transfer surface over IndexedRecord | Array[Byte] | String input — lives behind ONE factory: record (AvroRecordTraversal). Drilling (.field / .at / .union[Branch] / .fields / Dynamic selection) stays here, the single mechanism; drill first, flip last.

The Fields-vs-Leaf split lives entirely inside focus (per AvroFocus); the compatibility alias AvroFieldsTraversal points back here.

Attributes

Companion
object
Source
AvroTraversal.scala
Supertypes
trait Dynamic
trait Optic[Array[Byte], Array[Byte], A, A, MultiFocus[PSVec]]
class Object
trait Matchable
class Any
object ConfluentWire

The Confluent Schema Registry wire format for binary Avro payloads — a 5-byte header (magic byte 0x00, then the writer schema's registry id as a big-endian 4-byte int) in front of the plain Avro binary body — and the consumer surface built on it. This is the framing every Confluent-serialized Kafka topic carries; anything reading such a topic outside Confluent's own serializer stack needs exactly what lives here.

The Confluent Schema Registry wire format for binary Avro payloads — a 5-byte header (magic byte 0x00, then the writer schema's registry id as a big-endian 4-byte int) in front of the plain Avro binary body — and the consumer surface built on it. This is the framing every Confluent-serialized Kafka topic carries; anything reading such a topic outside Confluent's own serializer stack needs exactly what lives here.

'''Registry-agnostic by design.''' No registry client, no new dependency: every member that needs a schema looked up takes the SchemaById hook (or its effectful Int => F[Schema] sibling). Plug in a registry client, a static map, a cache — the deployment owns it. All failures are values from the AvroFailure taxonomy (or an AvroFailureException raised in the caller's F), never a bare NPE or an Avro internal exception.

The surface splits along two axes.

'''Axis 1 — drift policy.''' What happens when the writer schema named by the frame's id differs from the schema the consumer reads with:

  • '''Gate''' (resolve, confluent): compare parsing-canonical-form fingerprints and REFUSE any difference (AvroFailure.SchemaMismatch). The body bytes pass through untouched — byte-exact, zero re-encode. For payloads that must stay verbatim (hashing, signatures, archiving, pass-through forwarding), and for consumers where drift is a bug to surface, not a condition to absorb.
  • '''Translate''' (resolving, resolvingRecord, resolvingBytes, reader, recordReader): run Avro schema resolution (ResolvingDecoder) writer → reader, so compatible evolution — added fields with defaults, reordered fields, promotions — is absorbed instead of refused.

'''Axis 2 — output altitude.''' Each translating member hands back a different currency:

  • typed A (via AvroCodec): resolving for a KNOWN writer schema (pure optic), reader for a per-message lookup (effectful, Stream.evalMap-ready);
  • generic IndexedRecord (no case class needed): resolvingRecord / recordReader, same split;
  • reader-layout framed '''bytes''' (no decode at all): resolvingBytes — per-message lookup, writer-id-cached, for consumers that hash / slice / forward rather than deserialize.

Underneath sit the pure frame primitives strip / attach (header handling only, no schema involvement) — the building blocks for anything the members above don't cover.

Attributes

Source
ConfluentWire.scala
Supertypes
class Object
trait Matchable
class Any
Self type
enum PathStep

One step on an AvroPrism's flat navigation path — a field name, an array index, or a union branch.

One step on an AvroPrism's flat navigation path — a field name, an array index, or a union branch.

The path walker dispatches on the case to decide which of Avro's runtime representations to pierce: org.apache.avro.generic.IndexedRecord for named record fields, java.util.List (typically org.apache.avro.generic.GenericData.Array) for array indices, java.util.Map for map keys, and the runtime alternative directly for union branches.

'''Deliberate duplication.''' This enum is duplicated from dev.constructive.eo.circe.PathStep rather than shared. The two enums share Field(name) and Index(i) but eo-avro adds UnionBranch(branchName) — Avro's schema-driven feature with no JSON parallel. Sharing the type would force UnionBranch into eo-circe's dependency graph (where unions don't exist).

Visibility is package-default (public) so users can read PathStep values off AvroFailure instances exposed by the default Ior-bearing surface. The Field(name), Index(i), and UnionBranch(branchName) constructors are stable across v0.2.

Attributes

Source
PathStep.scala
Supertypes
trait Enum
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object given_Eq_IndexedRecord extends Eq[IndexedRecord]

Structural equality for IndexedRecord — schema + positional field values, recursing through nested records / arrays / maps.

Structural equality for IndexedRecord — schema + positional field values, recursing through nested records / arrays / maps.

A public given: downstream property tests and round-trip specs that compare records by value (rather than reference) pick this up via import dev.constructive.eo.avro.given.

Implementation note: defers to org.apache.avro.generic.GenericData.compare which already walks the schema-driven runtime shape recursively. Equal iff compare == 0.

Attributes

Source
package.scala
Supertypes
trait Eq[IndexedRecord]
trait Serializable
class Object
trait Matchable
class Any
Self type

Types

type AvroFieldsPrism[A] = AvroPrism[A]

Compatibility alias for the multi-field Prism shape on the Avro carrier.

Compatibility alias for the multi-field Prism shape on the Avro carrier.

The "where does the focus live" axis (single leaf vs NamedTuple assembled from selected fields) is factored into the internal AvroFocus storage, so a multi-field prism is just an AvroPrism[NT] whose focus happens to be a Fields focus; this alias keeps any downstream type ascriptions compiling.

Users seeing this alias in error messages should read it as "an AvroPrism[A] whose focus assembles A (a NamedTuple) from selected fields" — same surface, same byte-carried Optic supertype, same Ior diagnostics behind .record.

Mirrors dev.constructive.eo.circe.JsonFieldsPrism (which is similarly a type = alias for JsonPrism[A]).

Attributes

Source
AvroFieldsPrism.scala

Compatibility alias for the multi-field Traversal shape on the Avro carrier: an AvroTraversal[A] whose per-element focus assembles A (a NamedTuple) from selected fields — the parentPath / fieldNames / codec storage lives in the internal Fields focus, and the per-element walk delegates to it (byte-carried by default; the record-carried Ior surface sits behind .record). Mirrors dev.constructive.eo.circe.JsonFieldsTraversal; see AvroFieldsPrism for the same factoring on the prism side.

Compatibility alias for the multi-field Traversal shape on the Avro carrier: an AvroTraversal[A] whose per-element focus assembles A (a NamedTuple) from selected fields — the parentPath / fieldNames / codec storage lives in the internal Fields focus, and the per-element walk delegates to it (byte-carried by default; the record-carried Ior surface sits behind .record). Mirrors dev.constructive.eo.circe.JsonFieldsTraversal; see AvroFieldsPrism for the same factoring on the prism side.

Attributes

Source
AvroFieldsTraversal.scala

Value members

Concrete methods

def codecPrism[S](using codec: AvroCodec[S]): AvroPrism[S]

Root-level Prism from Avro to a native type S. Reads S's schema off the in-scope AvroCodec[S]. Alias for AvroPrism.codecPrism that reads more naturally when composed with .field.

Root-level Prism from Avro to a native type S. Reads S's schema off the in-scope AvroCodec[S]. Alias for AvroPrism.codecPrism that reads more naturally when composed with .field.

Attributes

Source
package.scala

Givens

Givens

given given_Eq_GenericRecord: Eq[GenericRecord]

Eq specialised to GenericRecord — the more common runtime type. Reuses the same compare-based implementation.

Eq specialised to GenericRecord — the more common runtime type. Reuses the same compare-based implementation.

Attributes

Source
package.scala

Structural equality for IndexedRecord — schema + positional field values, recursing through nested records / arrays / maps.

Structural equality for IndexedRecord — schema + positional field values, recursing through nested records / arrays / maps.

A public given: downstream property tests and round-trip specs that compare records by value (rather than reference) pick this up via import dev.constructive.eo.avro.given.

Implementation note: defers to org.apache.avro.generic.GenericData.compare which already walks the schema-driven runtime shape recursively. Equal iff compare == 0.

Attributes

Source
package.scala
given platedAvro: Plated[IndexedRecord]

A dev.constructive.eo.optics.Plated over the Avro record tree — the Avro analogue of dev.constructive.eo.circe.platedJson. The immediate children of a record are its directly-record-valued fields, so Plated.transform / rewrite / universe walk a whole nested-record document recursively (redact a field in every nested record, rewrite a value at any depth, …). Rebuild copies into a fresh GenericData.Record so the walk stays pure; stack-safe via the combinators' cats.Eval trampoline.

A dev.constructive.eo.optics.Plated over the Avro record tree — the Avro analogue of dev.constructive.eo.circe.platedJson. The immediate children of a record are its directly-record-valued fields, so Plated.transform / rewrite / universe walk a whole nested-record document recursively (redact a field in every nested record, rewrite a value at any depth, …). Rebuild copies into a fresh GenericData.Record so the walk stays pure; stack-safe via the combinators' cats.Eval trampoline.

'''Scope (v1):''' only fields whose value is itself an IndexedRecord are recursion points. Records nested inside array / map / union-of-record fields are leftover skeleton — descending those is a future extension (mirrors the core plate's exact-self-type rule).

Attributes

Source
package.scala