Modeling RTCPeerConnection in PureScript

Unlike JavaScript, PureScript doesn’t allow arrays to contain different data types. So in JavaScript, we can have arrays of objects that differ in what properties they have. Take the RTCPeerConnection as an example…

var configuration = {
  iceServers: [{
    urls: "stun:stun.services.mozilla.com",
    username: "louis@mozilla.com",
    credential: "webrtcdemo"
  },{
    urls: [
      "stun:stun.example.com",
      "stun:stun-1.example.com"
    ]
  }]
};

var pc = new RTCPeerConnection(configuration);

As I was working with WebRTC, I tried modeling this data in PureScript, and this is what I ended up with…

import Data.NonEmpty (singleton, NonEmpty)

data ServerType
  = STUN { urls :: NonEmpty Array String }
  | TURN {
      urls :: NonEmpty Array String,
      credentialType :: Maybe String,
      credential :: Maybe String,
      username :: Maybe String
    }

type Configuration = { iceServers :: Array ServerType }

configuration :: Configuration
configuration = {
  iceServers : [
    TURN {
      urls: singleton "stun:stun.services.mozilla.com",
      username: Just "louis@mozilla.com",
      credential: Just "webrtcdemo"
    },
    STUN {
      urls : "stun:stun.example.com" :| ["stun:stun-1.example.com"]
    }

  ]
}

For the sake of readability, I also defined a function that took an Array String and returned NonEmpty Array String. That way I could do toNonEmpty ["stun:stun.example.com", "stun:stun-1.example.com"] instead of having to use Data.NonEmpty.NonEmpty’s :| alias.

toNonEmpty :: forall a. (Monoid a) => Array a -> NonEmpty Array a
toNonEmpty a = case (Tuple (A.head a) (A.tail a)) of
  (Tuple Nothing _) -> singleton mempty
  (Tuple (Just h) Nothing) -> singleton h
  (Tuple (Just h) (Just t)) -> h :| t

Since this data type would eventually need to be converted into a native JavaScript object, I also defined this function, which uses toNullable to convert Nothing in PureScript land into null in javascript-land.

serverTypeToForeign :: ServerType -> Foreign
serverTypeToForeign (STUN s) = toForeign s
serverTypeToForeign (TURN t) = toForeign {
    url : t.urls,
    credentialType : toForeign $ toNullable (t.credentialType),
    credential : toForeign $ toNullable t.credential,
    username : toForeign $ toNullable t.username
  }