yaml: Parsing and rendering YAML

Section exercise: write a program using the rio template that receives a database connection string as an environment variable, by using a YAML config file.

Basic usage

#!/usr/bin/env stack
-- stack --resolver lts-12.21 script
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (withObject) -- should be provided by yaml...
import Data.Text (Text)
import Data.Vector (Vector)
import Data.Yaml

data Person = Person
  { personName :: !Text
  , personAge :: !Int
  }
  deriving (Show, Eq)
-- Could use Generic deriving, doing it by hand
instance ToJSON Person where
  toJSON Person {..} = object
    [ "name" .= personName
    , "age"  .= personAge
    ]
instance FromJSON Person where
  parseJSON = withObject "Person" $ \o -> Person
    <$> o .: "name"
    <*> o .: "age"

main :: IO ()
main = do
  let bs = encode
        [ Person "Alice" 25
        , Person "Bob" 30
        , Person "Charlie" 35
        ]
  people <-
    case decodeEither' bs of
      Left exc -> error $ "Could not parse: " ++ show exc
      Right people -> return people

  let fp = "people.yaml"
  encodeFile fp (people :: Vector Person)
  res <- decodeFileEither fp
  case res of
    Left exc -> error $ "Could not parse file: " ++ show exc
    Right people2
      | people == people2 -> mapM_ print people
      | otherwise -> error "Mismatch!"

Config files

YAML file itself

aws-secret: _env:AWS_SECRET
home-response: _env:HOME_RESPONSE:Hello World

Haskell code

#!/usr/bin/env stack
-- stack --resolver lts-12.21 script
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (withObject)
import Data.Text (Text)
import Data.Yaml
import Data.Yaml.Config

data Config = Config
  { awsSecret    :: !Text
  , homeResponse :: !Text
  }
  deriving Show
instance FromJSON Config where
  parseJSON = withObject "Config" $ \o -> Config
    <$> o .: "aws-secret"
    <*> o .: "home-response"

main :: IO ()
main = do
  config <- loadYamlSettingsArgs [] useEnv
  print (config :: Config)

Usage

$ ./Main.hs
Main.hs: loadYamlSettings: No configuration provided
$ ./Main.hs config.yaml
Main.hs: Could not convert to AppSettings: expected Text,
         encountered Null
$ AWS_SECRET=foobar ./Main.hs config.yaml
Config {awsSecret = "foobar", homeResponse = "Hello World"}
$ AWS_SECRET=foobar HOME_RESPONSE=Goodbye ./Main.hs config.yaml
Config {awsSecret = "foobar", homeResponse = "Goodbye"}

Exercises

Write a Haskell program to generate the following YAML file:

- title: Star Wars
  director: George Lucas
- title: Transformers
  director: Michael Bay

Write a Haskell program to convert a JSON formatted file to YAML format, and vice-versa.