Last active
November 14, 2016 19:48
-
-
Save aappddeevv/298751e3877159a1981e324b7227b302 to your computer and use it in GitHub Desktop.
Revisions
-
aappddeevv revised this gist
Nov 14, 2016 . 1 changed file with 39 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -164,6 +164,45 @@ class readerspecs extends FlatSpec with Matchers { (__ \ "el").read(areader))((_, _)) elAndAttrReader.read(prefixattr).toOption shouldBe Some(("theel", "string")) } val prefixattrcomplex = <kvs xmlns:i="mynamespace"> <el i:type="string">stringvalue</el> <el i:type="int">30</el> <el i:type="long">30</el> </kvs> it should "allow failing fast in order to get the right reader" in { implicit val elTypeReader = XmlReader.attribute[String]("{mynamespace}type") sealed trait ElType case class StringEl(v: String) extends ElType case class IntEl(v: Int) extends ElType case class LongEl(v: Long) extends ElType val kvReader = (__ \\ "el").read(seq(elTypeReader)) val result = kvReader.read(prefixattrcomplex) withClue("reading just the attributes:") { result.foreach(r => r should contain inOrderOnly ("string", "int", "long")) } case class WrongTypeError(expected: String) extends ValidationError // Fail if the namespaced attribute is not a match and return a nodeseq so we can compose it def filteritype(t: String) = nodeReader.filter(WrongTypeError(t))(n => elTypeReader.read(n).getOrElse("") == t) val stringElReader: XmlReader[StringEl] = filteritype("string").map(n => StringEl(n.text)) val intElReader = filteritype("int").map(n => IntEl(n.text.toInt)) val longElReader = filteritype("long") andThen nodeReader.map(n => LongEl(n.text.toInt)) // slight variation val eltypereader = (__.read(stringElReader) orElse __.read(intElReader) orElse __.read(longElReader)) val readEls = (__ \\ "el").read(seq(eltypereader)) withClue("reading full objects:") { readEls.read(prefixattrcomplex).foreach(r => r should contain inOrderOnly (StringEl("stringvalue"), IntEl(30), LongEl(30))) } } val f = <FormattedValues> -
aappddeevv revised this gist
Nov 12, 2016 . 1 changed file with 49 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -142,6 +142,55 @@ class readerspecs extends FlatSpec with Matchers { withClue("data:") { r.read(bodyXml).toOption shouldBe Option(IBody(false, IData("k", 1))) } } val prefixattr = <kvs xmlns:i="mynamespace"> <el i:type="string"> theel </el> </kvs> "attribute reader" should "read a prefixed attribute" in { val areader = XmlReader.attribute[String]("{mynamespace}type") val r = (__ \ "el")(prefixattr) val prefixResult = areader.read(r) assert(prefixResult.isSuccessful) prefixResult.toOption shouldBe Some("string") // combine this together for a real read val elAndAttrReader = ( (__ \ "el").read[String].map(_.trim) and (__ \ "el").read(areader))((_, _)) elAndAttrReader.read(prefixattr).toOption shouldBe Some(("theel", "string")) } val f = <FormattedValues> <b:KeyValuePairOfstringstring> <c:key>creditonhold</c:key> <c:value>No</c:value> </b:KeyValuePairOfstringstring> <b:KeyValuePairOfstringstring> <c:key>donotcontact</c:key> <c:value>Allow</c:value> </b:KeyValuePairOfstringstring> <b:KeyValuePairOfstringstring> <c:key>bstate</c:key> <c:value>1</c:value> </b:KeyValuePairOfstringstring> </FormattedValues> val kvpreader = ( (__ \ "key").read[String] and (__ \ "value").read[String])((_, _)) "reading kv pairs" should "read a sequency correctly" in { val r = (__ \\ "KeyValuePairOfstringstring").read(seq(kvpreader)) val tmp = r.read(f) r.map(v => v should contain inOrderOnly (("creditonhold", "No"), ("donotcontact", "Allow"), ("bstate", "1"))) } } ```` -
aappddeevv revised this gist
Nov 6, 2016 . 1 changed file with 3 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,12 +1,9 @@ If you use "readers" similar to play's json reader framework, you know that its a nice framework to compose your json converters. There is a similar framework for reading XML from lucidchart called xtract. xtract only provides XML reading. Its very similar to play json. Many of the examples that demonstrate the play json or xtract library are fairly simple and did not help me understand how to compose readers that need to have alternatives. For example, an XML fragment may have an element called `Fault` or an element called `Data` and you want to compose a reader that automatically handles both in one reader instance. How do you do that? Here's some scalatest examples that show one way to do that. You have some choices about how to navigate and how to compose so pay special attention to the details in the tests. I prefer to compose by using readers at the top element and use and/or on the builders, but you can choose to do it anyway you wish. ```scala import org.scalatest._ -
aappddeevv created this gist
Nov 6, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,150 @@ If you use "readers" similar to play's json reader framework, you know that its a nice framework to compose your json converters. There is a similar framework for reading XML from lucidchart called xtract. xtract only provides XML reading. Its very similar to play json. Many of the examples that demonstrate the play json or xtract library are fairly simple and did not help me understand how to compose readers that need to have alternatives. For example, an XML fragment may have an element called `Fault` or an element called `Data` and you want to compose a reader that automatically handles both in one reader instance. How do you do that? Here's some scalatest examples that show one way to do that. You have some choices about how to navigate and how to compose so pay special attention to the details in the tests. ```scala import org.scalatest._ class readerspecs extends FlatSpec with Matchers { import scala.xml._ import com.lucidchart.open.xtract._ import com.lucidchart.open.xtract.{ XmlReader, __ } import com.lucidchart.open.xtract.XmlReader._ import play.api.libs.functional.syntax._ import responseReaders._ val faultXml = <Fault> <ErrorCode>1</ErrorCode> <Message>Error</Message> </Fault> val faultInXml = <Response> <Body> <MultipleResponse> { faultXml } </MultipleResponse> </Body> </Response> val noFaultInXml = <Response> <Body> <MultipleResponse> <Data><key>k</key><value>1</value></Data> </MultipleResponse> </Body> </Response> val responseFaultXml = <Envelope> { faultInXml } </Envelope> val responseDataXml = <Envelope> { noFaultInXml } </Envelope> val bodyXml = <Envelope> <Flag>false</Flag> <Response> <Body> <MultipleResponse> <Data><key>k</key><value>1</value></Data> </MultipleResponse> </Body> </Response> </Envelope> sealed trait ResponseBody case class IFault(i: Int, e: String) extends ResponseBody case class IData(k: String, v: Int) extends ResponseBody case class IBody(flag: Boolean, r: ResponseBody) implicit val dataReader = ((__ \ "key").read[String] and (__ \ "value").read[Int])(IData.apply _) implicit val faultReader = ((__ \ "ErrorCode").read[Int] and (__ \ "Message").read[String])(IFault.apply _) val responsePathReader: XmlReader[NodeSeq] = (__ \ "Response").read val bodyPathReader: XmlReader[NodeSeq] = (__ \ "Body").read val faultPathReader = (__ \ "Fault").read[NodeSeq] val multipleResponsePathReader: XmlReader[NodeSeq] = (__ \ "MultipleResponse").read val multipleResponsePathReaderJump: XmlReader[NodeSeq] = (__ \\ "MultipleResponse").read // find anywhere in children "responseReaders" should "read a Fault" in { val r = faultReader.read(faultXml) r.toOption shouldBe Some(IFault(1, "Error")) } it should "find a fault or None inside a response body using a piece by piece XPath traversal" in { val bpath = (__ \\ "Body") val rpath = (__ \\ "MultipleResponse") val fpath = (__ \\ "Fault") val r: XmlReader[NodeSeq] = (bpath ++ rpath ++ fpath).read withClue("has fault:") { (r andThen faultReader).read(faultInXml).toOption shouldBe Some(IFault(1, "Error")) } withClue("no fault:") { (r andThen faultReader).read(noFaultInXml).toOption shouldBe None } } it should "read a constant using pure" in { val r = (XmlReader.pure(-1) and XmlReader.pure("Not an error"))(IFault.apply _) r.read(faultXml).toOption shouldBe Option(IFault(-1, "Not an error")) } it should "read something with a direct path" in { val r = (__ \\ "Data").read[IData] r.read(noFaultInXml).toOption shouldBe Option(IData("k", 1)) } it should "find a fault inside a response body using a composition of readers traversal" in { val r = (responsePathReader andThen bodyPathReader andThen multipleResponsePathReader andThen faultPathReader andThen faultReader) r.read(responseFaultXml).toOption shouldBe Option(IFault(1, "Error")) } it should "not find a fault inside a response body using a composition of readers traversal that are not correct" in { val r = (responsePathReader andThen bodyPathReader andThen /*multipleResponsePathReader andThen*/ faultPathReader andThen faultReader) r.read(responseFaultXml).toOption shouldBe None } it should "follow a path using composition starting with XmlReaders" in { // We use map because we are starting with readers that read NodeSeq, see the example below // Shows off all combinators or embedding an XPath in the middle as well but as a reader of nodeseq. val tmp: XmlReader[ResponseBody] = (responsePathReader andThen bodyPathReader andThen multipleResponsePathReader andThen ((faultPathReader andThen faultReader) or ((__ \ "Data").read[NodeSeq] andThen dataReader))) val r = tmp.map(IBody(true, _)) withClue("fault:") { r.read(responseFaultXml).toOption shouldBe Option(IBody(true, IFault(1, "Error"))) } withClue("data:") { r.read(responseDataXml).toOption shouldBe Option(IBody(true, IData("k", 1))) } } it should "follow a path using composition with builders" in { // Since the expression starts with a Builder 'and' Builder, we can use apply instead of map like above val tmp = ((__ \ "Flag").read[Boolean] and (responsePathReader andThen bodyPathReader andThen multipleResponsePathReader andThen ((faultPathReader andThen faultReader) or ((__ \ "Data").read[NodeSeq] andThen dataReader)))) val r = tmp(IBody.apply _) withClue("data:") { r.read(bodyXml).toOption shouldBe Option(IBody(false, IData("k", 1))) } } it should "like the previous test but jump to the location using an XPath then compose" in { // Since the expression starts with a Builder 'and' Builder, we can use apply instead of map like above val r = ((__ \ "Flag").read[Boolean] and (multipleResponsePathReaderJump andThen ((faultPathReader andThen faultReader) or ((__ \ "Data").read[NodeSeq] andThen dataReader))))(IBody.apply _) withClue("data:") { r.read(bodyXml).toOption shouldBe Option(IBody(false, IData("k", 1))) } } } ````