API 레퍼런스

← 코덱 개요 | API | 타입 규칙 | 예제 | RLP 비교


개요

이 문서는 세 가지 핵심 trait인 ByteEncoder, ByteDecoder, ByteCodec의 상세한 API 레퍼런스를 제공합니다. 타입별 인코딩 규칙은 타입 규칙을 참조하세요.

ByteEncoder

ByteEncoder[A]는 타입 A의 값을 deterministic 바이트 시퀀스로 인코딩하는 contravariant 타입 클래스입니다.

핵심 메서드

encode

def encode(value: A): ByteVector

값을 deterministic 바이트 시퀀스로 인코딩합니다.

예제:

import org.sigilaris.core.codec.byte.*

val encoder = ByteEncoder[Long]
encoder.encode(42L)
// res0: ByteVector = Chunk(
//   bytes = View(
//     at = scodec.bits.ByteVector$AtByteBuffer@9755a0b,
//     offset = 0L,
//     size = 8L
//   )
// )

조합자 (Combinators)

contramap

def contramap[B](f: B => A): ByteEncoder[B]

인코딩 전에 함수를 적용하여 새로운 인코더를 생성합니다. 이는 contravariant functor 연산입니다.

예제:

case class UserId(value: Long)

given ByteEncoder[UserId] = ByteEncoder[Long].contramap(_.value)
ByteEncoder[UserId].encode(UserId(100L))
// res1: ByteVector = Chunk(
//   bytes = View(
//     at = scodec.bits.ByteVector$AtByteBuffer@28aac6ab,
//     offset = 0L,
//     size = 8L
//   )
// )

사용 사례: 커스텀 타입을 인코딩 가능한 타입으로 변환합니다.

ByteDecoder

ByteDecoder[A]는 바이트 시퀀스를 타입 A의 값으로 디코딩하는 covariant 타입 클래스입니다.

핵심 메서드

decode

def decode(bytes: ByteVector): Either[DecodeFailure, DecodeResult[A]]

바이트를 값으로 디코딩하여 실패 또는 나머지 바이트를 포함한 결과를 반환합니다.

DecodeResult:

case class DecodeResult[A](value: A, remainder: ByteVector)

예제:

import scodec.bits.ByteVector

val decoder = ByteDecoder[Long]
val bytes = ByteVector(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a)
decoder.decode(bytes)
// res2: Either[DecodeFailure, DecodeResult[Long]] = Right(
//   value = DecodeResult(
//     value = 42L,
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )

조합자 (Combinators)

map

def map[B](f: A => B): ByteDecoder[B]

함수를 사용하여 디코딩된 값을 변환합니다. 이는 covariant functor 연산입니다.

예제:

import org.sigilaris.core.codec.byte.*

case class UserId(value: Long)

given ByteDecoder[UserId] = ByteDecoder[Long].map(UserId(_))
import scodec.bits.ByteVector

val bytes = ByteVector(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64)
ByteDecoder[UserId].decode(bytes)
// res4: Either[DecodeFailure, DecodeResult[UserId]] = Right(
//   value = DecodeResult(
//     value = UserId(value = 100L),
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )

emap

def emap[B](f: A => Either[DecodeFailure, B]): ByteDecoder[B]

검증과 함께 디코딩된 값을 변환합니다. 비즈니스 규칙에 따라 디코딩이 실패할 수 있습니다.

예제:

import org.sigilaris.core.codec.byte.*
import org.sigilaris.core.failure.DecodeFailure
import cats.syntax.either.*

case class PositiveInt(value: Int)

given ByteDecoder[PositiveInt] = ByteDecoder[Long].emap: n =>
  if n > 0 && n <= Int.MaxValue then
    PositiveInt(n.toInt).asRight
  else
    DecodeFailure(s"Value $n is not a positive Int").asLeft
import scodec.bits.ByteVector

val validBytes = ByteVector(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a)
val invalidBytes = ByteVector(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)
ByteDecoder[PositiveInt].decode(validBytes)
// res6: Either[DecodeFailure, DecodeResult[PositiveInt]] = Right(
//   value = DecodeResult(
//     value = PositiveInt(value = 10),
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )
ByteDecoder[PositiveInt].decode(invalidBytes).isLeft
// res7: Boolean = true

flatMap

def flatMap[B](f: A => ByteDecoder[B]): ByteDecoder[B]

디코딩 작업을 체이닝합니다. 다음 디코더는 이전에 디코딩된 값에 따라 달라집니다.

예제:

import org.sigilaris.core.codec.byte.*
import scodec.bits.ByteVector

// 길이가 디코더 동작을 결정하는 길이 접두사 데이터 디코딩
def decodeLengthPrefixed: ByteDecoder[String] =
  ByteDecoder[Long].flatMap: length =>
    new ByteDecoder[String]:
      def decode(bytes: ByteVector) =
        if bytes.size >= length then
          val (data, remainder) = bytes.splitAt(length)
          Right(DecodeResult(data.decodeUtf8.getOrElse(""), remainder))
        else
          Left(org.sigilaris.core.failure.DecodeFailure(s"Insufficient bytes: need $length, got ${bytes.size}"))

사용 사례: 구조가 이전에 디코딩된 값에 따라 달라지는 컨텍스트 의존적 디코딩.

ByteCodec

ByteCodec[A]ByteEncoder[A]ByteDecoder[A]를 단일 타입 클래스로 결합합니다.

trait ByteCodec[A] extends ByteDecoder[A] with ByteEncoder[A]

사용법

타입에 인코더와 디코더 인스턴스가 모두 있으면 함께 summon할 수 있습니다:

val codec = ByteCodec[Long]

val encoded = codec.encode(42L)
val decoded = codec.decode(encoded)
encoded
// res9: ByteVector = Chunk(
//   bytes = View(
//     at = scodec.bits.ByteVector$AtByteBuffer@5531223e,
//     offset = 0L,
//     size = 8L
//   )
// )
decoded
// res10: Either[DecodeFailure, DecodeResult[Long]] = Right(
//   value = DecodeResult(
//     value = 42L,
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )

자동 Derivation

ByteCodec는 product 타입(case class, tuple)에 대한 자동 derivation을 제공합니다:

import org.sigilaris.core.codec.byte.*

case class Transaction(from: Long, to: Long, amount: Long)

// 인스턴스가 자동으로 derive됨
val tx = Transaction(1L, 2L, 100L)
val encoded = ByteEncoder[Transaction].encode(tx)
// encoded: ByteVector = ByteVector(24 bytes, 0x000000000000000100000000000000020000000000000064)
val decoded = ByteDecoder[Transaction].decode(encoded)
// decoded: Either[DecodeFailure, DecodeResult[Transaction]] = Right(
//   value = DecodeResult(
//     value = Transaction(from = 1L, to = 2L, amount = 100L),
//     remainder = ByteVector(empty)
//   )
// )

Given 인스턴스

companion object는 일반적인 타입에 대한 given 인스턴스를 제공합니다. 자세한 인코딩 명세는 타입 규칙을 참조하세요.

기본 타입

숫자 타입

컬렉션

Product 타입

DecodeResult에서 값 추출

DecodeResult[A]는 디코딩된 값과 나머지 바이트를 모두 포함합니다. 값만 추출하려면:

예제:

import org.sigilaris.core.codec.byte.*
import scodec.bits.ByteVector
val result = ByteDecoder[Long].decode(ByteVector(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a))
// result: Either[DecodeFailure, DecodeResult[Long]] = Right(
//   value = DecodeResult(
//     value = 42L,
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )
result.map(_.value)  // 값만 추출
// res13: Either[DecodeFailure, Long] = Right(value = 42L)

에러 처리

DecodeFailure

디코딩 실패는 DecodeFailure로 표현됩니다:

case class DecodeFailure(msg: String)

일반적인 실패 시나리오:

예제:

import org.sigilaris.core.codec.byte.*
import scodec.bits.ByteVector
// Long에 대한 바이트 부족 (8바이트 필요)
ByteDecoder[Long].decode(ByteVector(0x01, 0x02))
// res15: Either[DecodeFailure, DecodeResult[Long]] = Left(
//   value = DecodeFailure(
//     msg = "Too short bytes to decode Long; required 8 bytes, but received 2 bytes: ByteVector(2 bytes, 0x0102)"
//   )
// )

// 빈 바이트
ByteDecoder[Long].decode(ByteVector.empty)
// res16: Either[DecodeFailure, DecodeResult[Long]] = Left(
//   value = DecodeFailure(
//     msg = "Too short bytes to decode Long; required 8 bytes, but received 0 bytes: ByteVector(empty)"
//   )
// )

모범 사례

1. Encoder에는 contramap 사용

커스텀 타입을 표준 타입으로 변환:

case class Timestamp(millis: Long)
given ByteEncoder[Timestamp] = ByteEncoder[Long].contramap(_.millis)

2. 검증에는 emap 사용

디코딩 중 검증 로직 추가:

import org.sigilaris.core.codec.byte.*
import org.sigilaris.core.failure.DecodeFailure
import cats.syntax.either.*

case class PositiveLong(value: Long)

given ByteDecoder[PositiveLong] = ByteDecoder[Long].emap: n =>
  if n > 0 then PositiveLong(n).asRight
  else DecodeFailure(s"Value must be positive, got $n").asLeft

3. 자동 Derivation 활용

컴파일러가 case class에 대한 인스턴스를 derive하도록:

case class Account(id: Long, balance: BigInt)
// ByteEncoder[Account]와 ByteDecoder[Account]가 자동으로 사용 가능

4. flatMap으로 Decoder 체이닝

복잡한 디코딩 로직:

ByteDecoder[Long].flatMap: discriminator =>
  discriminator match
    case 1 => ByteDecoder[TypeA]
    case 2 => ByteDecoder[TypeB]
    case _ => ByteDecoder.fail(s"Unknown type: $discriminator")

성능 참고사항

참고


← 코덱 개요 | API | 타입 규칙 | 예제 | RLP 비교