This site is still a work-in-progress, thank you for your understanding.

Edit this page

Parsing and rendering YAML

The yaml library

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.

  • High level API based on aeson
  • Low-level, streaming API (we won’t cover it)
  • Config file helpers
  • Leverages C libyaml for functionality

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!"
  • Encode/decode to both ByteString and files
  • Prefer the explicit exception functions
    • 20/20 hindsight: they’d be the default

Config files

  • Common use case for YAML
  • Would be nice to allow for env var override
  • This is the default Yesod scaffolding config approach
  • Stolen from my Yesod talk, apologies :)

YAML file itself

aws-secret: _env:AWS_SECRET
home-response: _env:HOME_RESPONSE:Hello World
  • Special syntax in YAML to allow env overriding
  • aws-secret: must have an env var
  • home-response: optional

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)
  • FromJSON: normal aeson/yaml code
  • No ToJSON needed
  • Get config file name from command line arguments
  • Use environment variables when available

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"}
  • Must provide config file(s) on command line
  • Must provide AWS_SECRET
  • If provided, HOME_RESPONSE changes that response payload

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.