-
Notifications
You must be signed in to change notification settings - Fork 0
Feature: Add Case Class Support to ObjectWeaver #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
98b3a53
1e13373
b8f3635
802dfb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package wvlet.ai.core.weaver | ||
|
|
||
| import scala.deriving.Mirror | ||
| import scala.compiletime.{erasedValue, summonInline} | ||
| import wvlet.ai.core.msgpack.spi.{Packer, Unpacker} | ||
|
|
||
| // Removed duplicate ObjectWeaver trait. | ||
| // The canonical one is in ObjectWeaver.scala | ||
|
|
||
| class CaseClassWeaver[A](using m: Mirror.ProductOf[A]) extends ObjectWeaver[A] { | ||
|
|
||
| // Note: elementWeavers are now of type ObjectWeaver from the canonical definition | ||
| private inline def summonElementWeavers[Elems <: Tuple]: List[ObjectWeaver[?]] = | ||
| inline erasedValue[Elems] match { | ||
| case _: (elem *: elemsTail) => | ||
| summonInline[ObjectWeaver[elem]] :: summonElementWeavers[elemsTail] | ||
| case _: EmptyTuple => | ||
| Nil | ||
| } | ||
|
|
||
| private val elementWeavers: List[ObjectWeaver[?]] = summonElementWeavers[m.MirroredElemTypes] | ||
|
|
||
| override def pack(packer: Packer, v: A, config: WeaverConfig): Unit = { | ||
| val product = v.asInstanceOf[Product] | ||
| if (product.productArity != elementWeavers.size) { | ||
| // TODO: More specific error handling using WeaverContext | ||
| throw new IllegalArgumentException(s"Element count mismatch. Expected: ${elementWeavers.size}, Got: ${product.productArity}") | ||
| } | ||
| packer.packArrayHeader(elementWeavers.size) | ||
| product.productIterator.zip(elementWeavers).foreach { case (elem, weaver) => | ||
| // This cast is generally safe due to how elementWeavers is constructed. | ||
| // The individual element's weaver will handle its specific packing. | ||
| (weaver.asInstanceOf[ObjectWeaver[Any]]).pack(packer, elem, config) | ||
| } | ||
| } | ||
|
|
||
| override def unpack(unpacker: Unpacker, context: WeaverContext): Unit = { | ||
| val numElements = unpacker.unpackArrayHeader() | ||
| if (numElements != elementWeavers.size) { | ||
| context.setError(new IllegalArgumentException(s"Element count mismatch. Expected: ${elementWeavers.size}, Got: ${numElements}")) | ||
| // TODO: Potentially consume unexpected fields from unpacker to allow recovery or partial unpack | ||
|
||
| return | ||
| } | ||
|
|
||
| val elements = new Array[Any](elementWeavers.size) | ||
| var i = 0 | ||
| var failed = false | ||
| while (i < elementWeavers.size && !failed) { | ||
| val weaver = elementWeavers(i) | ||
| // Create a new context for each element to isolate errors and values | ||
| val elementContext = WeaverContext(context.config) | ||
| weaver.unpack(unpacker, elementContext) | ||
|
|
||
| if (elementContext.hasError) { | ||
| context.setError(new RuntimeException(s"Failed to unpack element $i: ${elementContext.getError.get.getMessage}", elementContext.getError.get)) | ||
| failed = true | ||
| } else { | ||
| elements(i) = elementContext.getLastValue | ||
| } | ||
| i += 1 | ||
| } | ||
|
|
||
| if (!failed) { | ||
| try { | ||
| val instance = m.fromProduct(new Product { | ||
| override def productArity: Int = elements.length | ||
| override def productElement(n: Int): Any = elements(n) | ||
| override def canEqual(that: Any): Boolean = that.isInstanceOf[Product] && that.asInstanceOf[Product].productArity == productArity | ||
| }) | ||
| context.setLastValue(instance) | ||
| } catch { | ||
| case e: Throwable => | ||
| context.setError(new RuntimeException("Failed to instantiate case class from product", e)) | ||
| } | ||
| } | ||
| // If failed, context will already have an error set. | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
TODOcomment on the preceding line (// TODO: More specific error handling using WeaverContext) suggests usingWeaverContextfor error handling here. However, thepackmethod signatureoverride def pack(packer: Packer, v: A, config: WeaverConfig): Unitdoesn't include aWeaverContextparameter, unlike theunpackmethod whereWeaverContextis used for error reporting.Could you clarify the intention behind this
TODO?WeaverPackingException) that could be thrown here instead ofIllegalArgumentException?WeaverContextinvolvement is desired, how was it envisioned to be integrated with the currentpacksignature, or would this require changes to theObjectWeavertrait?IllegalArgumentExceptionconsidered sufficient for this kind of precondition failure (element count mismatch)?Addressing or clarifying this
TODOwould help ensure the error handling strategy is consistent and clear.