1. 4
  1.  

  2. 2

    We’ve started playing around with the rest set of libraries at work. Some of the nice things rest does:

    • Routes with built in versioning
    • Automatic documentation and example generation
    • Generation of Ruby, Haskell and JavaScript client libraries (and probably Python and Scala additions from us, soon)

    I was originally skeptical because most of the examples rely on GHC Generics and Data.Typeable which I find to be anti-modular. Luckily you can remove most of the generic programming parts and create your instances in the usual ways.

    Here’s an example from our code:

    data AccountError = InvalidAccountNumber String
                      | EmptyPremiseId
                      | EmptyServicePointId
                        deriving (Show, Typeable)
    
    instance S.JSONSchema AccountError where
      schema _ =
        S.Choice [
          S.Object [
            S.Field "tag" True (S.Constant (String "invalid_account_number")),
            S.Field "account_number" True (S.Value S.unboundedLength)
          ],
          S.Object [
            S.Field "tag" True (S.Constant (String "empty_service_point_id"))
          ],
          S.Object [
            S.Field "tag" True (S.Constant (String "empty_premise_id"))
          ]
        ]
    
    
    parseAccountError :: Object -> Parser AccountError
    parseAccountError o = do
      tag <- o .: "tag"
      case (tag :: String) of
        "invalid_account_number" -> InvalidAccountNumber <$> o .: "account_number"
        "empty_premise_id" -> pure EmptyPremiseId
        "empty_service_point_id" -> pure EmptyServicePointId
        s -> fail $ "Unkown tag " ++ show s
    
    instance FromJSON AccountError where
      parseJSON = withObject "AccountError" parseAccountError
    
    instance ToJSON AccountError where
      toJSON (InvalidAccountNumber s) = object [("tag", "invalid_account_number"), ("account_number", fromString s)]
      toJSON EmptyPremiseId = object [("tag", "empty_premise_id")]
      toJSON EmptyServicePointId = object [("tag", "empty_service_point_id")]
    
    accountErrorCreate :: Handler (ReaderT KafkaConfig IO)
    accountErrorCreate = mkInputHandler (jsonI . someI) $ lift . sendAccountError
    

    I want to try and unify the schema, parsing and pretty-printing code. Not sure if I’ll be able to do it, though.