Support for XML request responses in sttp and tapir

Rafał Ambrożewicz

11 Oct 2022.2 minutes read

Support for XML request responses in sttp and tapir webp image

Nearly all applications communicate with web servers. This introduces a need to speak a common language so that all parties understand the messages. There are two main standards - JSON and XML. While JSON is by far more popular, you might find yourself in a need to consume or serve XML in your application. Thankfully, supporting XML could be easily achieved likewise in sttp and tapir using the scalaxb tool.

This quick teaser highlights the main steps to take to enable such integration and points to documentation for more.

Initial setup - using scalaxb

Scalaxb is a mature, XML data-binding tool for Scala. Using XML Schema definition file (.xsd), it can generate models as well as encoding/decoding logic that we would later use in sttp/tapir code. Integration is fairly easy, with only a few steps described on the project's website, needed files will be generated, and we are ready to look at the sttp / tapir code.

Using sttp

Adding support for XML in sttp is a matter of providing an implicit BodySerializer that describes how to turn a given object into an XML String and deserialization function turning XML String into an object of a given type. Optionally, we could define a helper function wrapping deserialized value into "Response format" to use when defining calls.

// 1. define `BodySerializer`
implicit def scalaxbBodySerializer[B](implicit format: CanWriteXML[B], label: XmlElementLabel): BodySerializer[B] = ...

// 2. define deserialization function
implicit def deserializeXml[B](implicit decoder: XMLFormat[B]): String => Either[Exception, B] = ...

// 3. add helper deserialization function
def asXml[B: XMLFormat]: ResponseAs[Either[ResponseException[String, Exception], B], Any] = ...

// 4. use it when defining calls
 val br: Identity[Response[Either[ResponseException[String, Exception], ClassGeneratedByScalaxb]]] = basicRequest
    .body(requestPayload) // XML serialized with `BodySerializer`
    .response(asXml[ClassGeneratedByScalaxb]) // XML deserialized with `asXml`

A detailed integration guide can be found in the documentation.

Using tapir

To allow for XML support in tapir, we would need to implement a Codec or, to be more precise, an XmlCodec. It describes both how to turn XML String into an object and how to turn an object into an XML String. Also, it is worth introducing a helper function wrapping the aforementioned logic into the "Endpoint body" to use when defining calls.

// 1. define `XmlCodec`
implicit def scalaxbCodec[T: XMLFormat: Schema](implicit label: XmlElementLabel): XmlCodec[T] = Codec.xml((s: String) => ... )((t: T) =>  ...)

// 2. define helper function
def xmlBody[T: XMLFormat: Schema](implicit l: XmlElementLabel): EndpointIO.Body[String, T] = ...

// 3. use it in endpoint definitions
  val xmlEndpoint: PublicEndpoint[ClassGeneratedByScalaxb, Unit, ClassGeneratedByScalaxb, Any] =
    .in(xmlBody[ClassGeneratedByScalaxb]) // XML deserialized with `xmlBody`
    .out(xmlBody[ClassGeneratedByScalaxb]) // XML serialized with `xmlBody`

Likewise, you can check the full guide in the documentation. Furthermore, you can check the example GitHub project showcasing scalaxb - tapir integration.

Final thoughts

Predictably, adding an XML integration to sttp or tapir is fairly straightforward. This short teaser showed that defining coding/encoding logic and helper functions for seamless integration with API for defining call/endpoints is pretty painless.

If you need more details, don't forget to check the full documentation on integration with XML for sttp and tapir. And if you have any questions or suggestions about this text, don't hesitate to let me know. Thanks!

Blog Comments powered by Disqus.