Scodec Support
This module provides integration with Scodec, Scala's combinator library for encoding and decoding binary data.
Installation
In your build tool, add the following dependency:
libraryDependencies += "io.github.iltotore" %% "iron-scodec" % "version"
Usage
The iron-scodec
module provides Codec
instances for refined types. Simply import the instances and use them as you would with regular scodec codecs:
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.Positive
import io.github.iltotore.iron.scodec.given
import scodec.*
import scodec.bits.*
import scodec.codecs.*
// Use scodec codecs with refined types
val positiveIntCodec: Codec[Int :| Positive] = Codec[Int :| Positive]
// Encoding
val value: Int :| Positive = 42.refine[Positive]
val encoded: Attempt[BitVector] = positiveIntCodec.encode(value)
// Decoding
val decoded: Attempt[DecodeResult[Int :| Positive]] =
encoded.flatMap(positiveIntCodec.decode)
// Decoding invalid values will fail
val negativeBits = int32.encode(-5).require
val failedDecode = positiveIntCodec.decode(negativeBits) // This will fail
Working with Newtypes
The module also supports Iron's newtypes:
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.Positive
import io.github.iltotore.iron.scodec.given
import scodec.*
import scodec.codecs.*
// Define a newtype
type Temperature = Temperature.T
object Temperature extends RefinedType[Double, Positive]
// Codec for the newtype
val tempCodec: Codec[Temperature] = Codec[Temperature]
// Usage
val temp = Temperature(25.5)
val encoded = tempCodec.encode(temp)
val decoded = encoded.flatMap(tempCodec.decode)
Scala 3 Derives Support
The module fully supports Scala 3's derives
syntax for automatic codec derivation:
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.Positive
import io.github.iltotore.iron.constraint.string.MinLength
import io.github.iltotore.iron.scodec.given
import scodec.*
// Define refined types
type Age = Int :| Positive
type Name = String :| MinLength[1]
// Derive codecs for case classes
case class Person(name: Name, age: Age) derives Codec
// The derived codec handles validation automatically
val person = Person("Alice", 25)
val encoded = Codec[Person].encode(person)
val decoded = encoded.flatMap(Codec[Person].decode)
How it Works
The module provides two main given instances:
- For newtypes: Uses
RefinedType.Mirror
to provide codecs for newtypes - For refined types: Uses the base type's codec and validates the constraint during decoding
When decoding, if the value doesn't satisfy the constraint, the decode operation will fail with an appropriate error message. The integration seamlessly works with scodec's built-in derivation mechanism for case classes and sealed traits.
In this article