Validating XML with Writer and Kleisli in Scala

153 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 best solutions below

2
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.