Michael
Michael

Reputation: 42100

Types for strings escaped/encoded differently

Recently I am dealing with escaping/encoding issues. I have a bunch of APIs that receive and return Strings encoded/escaped differently. In order to clean up the mess I'd like to introduce new types XmlEscapedString, HtmlEscapedString, UrlEncodedString, etc. and use them instead of Strings.

The problem is that the compiler cannot check the encoding/escaping and I'll have runtime errors.

I can also provide "conversion" functions that escape/encode input as necessary. Does it make sense ?

Upvotes: 2

Views: 81

Answers (1)

lmm
lmm

Reputation: 17431

The compiler can enforce that you pass the types through your encoding/decoding functions; this should be enough, provided you get things right at the boundaries (if you have a correctly encoded XmlEscapedString and convert it to a UrlEncodedString, the result is always going to be correctly encoded, no?). You could use constructors or conversion methods that check the escaping initially, though you might pay a performance penalty for doing so.

(Theoretically it might be possible to check a string's escaping at compile time using type-level programming, but this would be exceedingly difficult and only work on literals anyway, when it sounds like the problem is Strings coming in from other APIs).

My own compromise position would probably be to use tagged types (using Scalaz tags) and have the conversion from untagged String to tagged string perform the checking, i.e.:

import scalaz._, Scalaz._

sealed trait XmlEscaped

def xmlEscape(rawString: String): String @@ XmlEscaped = {
  //perform escaping, guaranteed to return a correctly-escaped String
  Tag[String, XmlEscaped](escapedString)
}
def castToXmlEscaped(escapedStringFromJavaApi: String) = {
  require(...) //confirm that string is properly escaped
  Tag[String, XmlEscaped](escapedStringFromJavaApi)
}

def someMethodThatRequiresAnEscapedString(string: String @@ XmlEscaped)

Then we use castToXmlEscaped for Strings that are already supposed to be XML-escaped, so we check there, but we only have to check once; the rest of the time we pass it around as a String @@ XmlEscaped, and the compiler will enforce that we never pass a non-escaped string to a method that expects one.

Upvotes: 1

Related Questions