Decoding issue with Option[Unit]
The encoder for Option[Unit] seems to be bugged:
case class Bar(data: Option[Unit]) derives ElementDecoder
val xml = "<bar><data/></bar>"
val actual = XmlDecoder.fromElementDecoder[Bar]("bar").decode(xml)
Left(ru.tinkoff.phobos.decoding.DecodingError: Error while decoding XML: Element is already decoded (Most likely it occurred more than once)
In element 'data'
in element 'bar'
)
It may comes from how the decoder for Option works with the one used for Unit:
implicit def optionDecoder[A](implicit decoder: ElementDecoder[A]): ElementDecoder[Option[A]] =
new ElementDecoder[Option[A]] {
def decodeAsElement(c: Cursor, localName: String, namespaceUri: Option[String]): ElementDecoder[Option[A]] = {
if (c.isStartElement) {
ElementDecoder.errorIfWrongName[Option[A]](c, localName, namespaceUri).getOrElse {
if (ElementDecoder.isNil(c) || (c.isEmptyElement && c.getAttributeInfo.getAttributeCount == 0)) {
c.next()
new ConstDecoder(None)
} else {
// If the decoder happens to be a ConstDecoder[Unit], `decodeAsElement` will return a FailedDecoder
// instead of a ConstDecoder[Unit].
decoder.map[Option[A]](a => Some(a)).decodeAsElement(c, localName, namespaceUri)
}
}
} else {
new FailedDecoder[Option[A]](c.error(s"Unexpected event: '${c.getEventType}'"))
}
}
Decoding Option[Unit] may be useful to decode empty xml tags acting as flags.
I've done some investigation and it seems there's an issue with the Decoder derivation. For some reasons I don't get yet, the decodeAsElement is called recursively until a FailedDecoder is returned. The result function therefore returns a Left(...).
I've managed to fix this with this:
given ElementDecoder[Unit] = ElementDecoder.stringDecoder.map(_ => ())
given ElementEncoder[Unit] = ElementEncoder.stringEncoder.contramap(_ => "")
Does this solution make sense?
It's certainly a bug. Decoder for Unit does not move the decoding "cursor" from <data/> to the next XML tag, hence the derived decoder tries to decode <data/> once again, and the decoding process fails.
Your fix makes sense, as it will move the "cursor" out of the <data/> element. Encoder change is also useful, as it adds some sense to Unit elements
Will publish this change in 0.17.0