Skip to content

Instantly share code, notes, and snippets.

@Daenyth
Created November 12, 2019 15:19
Show Gist options
  • Save Daenyth/85705ccf2e18aeeaebf26365845491d8 to your computer and use it in GitHub Desktop.
Save Daenyth/85705ccf2e18aeeaebf26365845491d8 to your computer and use it in GitHub Desktop.
import cats.implicits._
import cats.data.NonEmptyChain
import scala.xml.{Elem, Node}
object ScalaXmlUtils {
type XmlResult[A] = Either[NonEmptyChain[String], A]
/** Get the `<tagLabel>` child of `node` or fail */
def get(node: Node, tagLabel: String): XmlResult[Node] =
getOpt(node, tagLabel)
.toRight(fail(s"${node.label} does not contain a <$tagLabel> child"))
/** Get the `<tagLabel>` child of `node` or `None` */
def getOpt(node: Node, tagLabel: String): Option[Node] =
(node \ tagLabel).headOption
/** Get the `text` from the <childName> of `node` or fail */
def getText(node: Node, childName: String): XmlResult[String] =
get(node, childName).flatMap(textNonEmpty)
/** Get the `text` of `node` or fail */
def textNonEmpty(node: Node): XmlResult[String] =
ok(node).map(_.text).ensure(fail(s"$node had no inner text"))(_.nonEmpty)
private def ok[A](a: A): XmlResult[A] = Right(a)
private def fail(msg: String) = Left(NonEmptyChain.one(msg))
/** Example parsing this xml from `Foo` service:
<Error>
<Type>str</Type>
<Code>str</Code>
<Message>str</<Message>
<Detail>optional list of arbitrary nodes</Detail>
</Error>
*/
def parseError(node: Node): XmlResult[FooError] =
get(node, "Error").flatMap { err =>
(
getText(err, "Type"),
getText(err, "Code"),
getText(err, "Message"),
).parMapN(
FooError(_,
_,
_,
getOpt(err, "Detail").toList.flatMap(_.toList.map(
_.toString)))) // Note: parMapN to combine failures via `cats.data.Validated`
}
/** Models the <Error> node of Foo Xml */
case class FooError(errorType: String,
code: String,
message: String,
details: List[String])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment