Validating XML with Writer and Kleisli in Scala

140 views Asked by At

This is a follow-up to my previous question

Suppose I need to validate an XML like this:

<a><a1>xxx<a1/><a2>yyy</a2><a3>zzz</a3></a>

I need to make sure that the root element has label a and also has children <a1>xxx</a1>, <a2>yyy</a2>, <a3>zzz</a3> in this order.

I'd like to use List[String] to collect errors and define a function to validate a single XML element like this:

type ValidateSingleElement = Elem => List[String]

Now I can write functions to validate the label, text, and attributes of a given XML element:

val label : String => ValidateSingleElement = ...
val text  : String => ValidateSingleElement = ...
val attr  : (String, String) => ValidateSingleElement = ...

I can compose these functions with |+| since ValidateSingleElement is a monoid.

val a1 = label("a1") |+| text("xxx") // validate both label and text

Now I need a function to validate the children of a given element. In order to write such a function I need another function to validate a sequence of elements

val children: ValidateElements => ValidateSingleElement = ...

The ValidateElements is defined as follows:

type ValidateElements = List[Elem] => Writer[List[String], List[Elem]]

I am using the List[String] and Writer monad to collect errors while traversing the sequence of elements.

Now I can write a function to validate the children of a given element:

val children: ValidateElements => ValidateSingleElement = 
  validateElements => {e =>
    val kids = e.child collect {case e:Elem => e}
    val writer = validateElements(kids.toList)
    writer.written
  }

... and validate the first element of the sequence:

 val child: ValidateSingleElement => ValidateElements = validate => {
   _ match {
     case e:es => Writer(validate(e), es)
     case _    => Writer(List("Unexpected end of input"), Nil)
   }
 }

Finally I can re-define ValidateElements as Kleisli

type ErrorsWriter[A]  = Writer[List[String], A]
type ValidateElements = Kliesli[ErrorsWriter, List[Elem], List[Elem]]

... and re-write the child to return the Kleisli instead of the function.

Given both the child and children I can write a -- a validating function for the XML from above:

val a1 = label("a1") |+| text("xxx")
val a2 = label("a2") |+| text("yyy")
val a3 = label("a3") |+| text("zzz")
val a  = label("a")  |+| children(child(a1) >=> child(a2) >=> child(a3))

Does it make sense ? How would you correct/extend this design ?

1

There are 1 answers

2
Matthias Berndt On

Well, in most cases you don't want to only validate an XML document, you want to create some meaningful business object from it, and your code doesn't seem to allow for that. I think Play's type-class based Json library is a good model for how to do this. It allows you to define Reads objects, where a Reads[A] is essentially a JsValue => Either[Errors, A]. These can be flexibly combined with a bunch of combinators shipped with the library.