packages feed

postgres-options 0.1.0.1 → 0.2.0.0

raw patch · 3 files changed

+263/−100 lines, 3 filesdep +generic-monoiddep +hspecdep +postgres-optionsdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: generic-monoid, hspec, postgres-options, split, uri-bytestring

Dependency ranges changed: base

API changes (from Hackage documentation)

- Database.PostgreSQL.Simple.Options: [oClientEncoding] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oConnectTimeout] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oDbname] :: Options -> String
- Database.PostgreSQL.Simple.Options: [oFallbackApplicationName] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oGsslib] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oHost] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oHostaddr] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oKeepalivesCount] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oKeepalivesIdle] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oKeepalives] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oKrbsrvname] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oOptions] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oPassword] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oPort] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oRequirepeer] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oRequiressl] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oService] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oSslcert] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oSslcompression] :: Options -> Maybe Int
- Database.PostgreSQL.Simple.Options: [oSslkey] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oSslmode] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oSslrootcert] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: [oUser] :: Options -> Maybe String
- Database.PostgreSQL.Simple.Options: toArgs :: Options -> [String]
+ Database.PostgreSQL.Simple.Options: [clientEncoding] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [connectTimeout] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [dbname] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [fallbackApplicationName] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [gsslib] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [host] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [hostaddr] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [keepalivesCount] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [keepalivesIdle] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [keepalives] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [krbsrvname] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [options] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [password] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [port] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [requirepeer] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [requiressl] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [service] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [sslcert] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [sslcompression] :: Options -> Last Int
+ Database.PostgreSQL.Simple.Options: [sslkey] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [sslmode] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [sslrootcert] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: [user] :: Options -> Last String
+ Database.PostgreSQL.Simple.Options: instance GHC.Base.Monoid Database.PostgreSQL.Simple.Options.Options
+ Database.PostgreSQL.Simple.Options: instance GHC.Base.Semigroup Database.PostgreSQL.Simple.Options.Options
+ Database.PostgreSQL.Simple.Options: parseConnectionString :: String -> Either String Options
- Database.PostgreSQL.Simple.Options: Options :: Maybe String -> Maybe String -> Maybe Int -> Maybe String -> Maybe String -> String -> Maybe Int -> Maybe String -> Maybe String -> Maybe String -> Maybe Int -> Maybe Int -> Maybe Int -> Maybe String -> Maybe Int -> Maybe Int -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Options
+ Database.PostgreSQL.Simple.Options: Options :: Last String -> Last String -> Last Int -> Last String -> Last String -> Last String -> Last Int -> Last String -> Last String -> Last String -> Last Int -> Last Int -> Last Int -> Last String -> Last Int -> Last Int -> Last String -> Last String -> Last String -> Last String -> Last String -> Last String -> Last String -> Options
- Database.PostgreSQL.Simple.Options: defaultOptions :: String -> Options
+ Database.PostgreSQL.Simple.Options: defaultOptions :: Options

Files

postgres-options.cabal view
@@ -1,5 +1,5 @@ name:                postgres-options-version:             0.1.0.1+version:             0.2.0.0 synopsis:            An Options type representing options for postgres connections description:         This package exports an Options type representing options for postgres connections homepage:            https://github.com/jfischoff/postgres-options#readme@@ -16,10 +16,39 @@ library   hs-source-dirs:      src   exposed-modules:     Database.PostgreSQL.Simple.Options-  build-depends: base >= 4.6 && < 5, bytestring+  build-depends:+      base >= 4.6 && < 5+    , bytestring+    , split+    , uri-bytestring+    , generic-monoid   default-language:    Haskell2010   ghc-options: -Wall                -fno-warn-unused-do-bind+  default-extensions:+      LambdaCase+    , RecordWildCards+    , GADTs+    , OverloadedStrings+    , DerivingStrategies+    , DerivingVia+    , TupleSections+    , DeriveGeneric++test-suite unit-tests+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  build-depends:       base+                     , hspec+                     , postgres-options+  ghc-options: -Wall+               -fno-warn-unused-do-bind+               -threaded+               -rtsopts+               -with-rtsopts=-N+  default-language:    Haskell2010+  source-repository head   type:     git
src/Database/PostgreSQL/Simple/Options.hs view
@@ -1,115 +1,204 @@-{-| A resuable optparse-applicative parser for creating a postgresql-simple-   'Connection'--}--{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-}+{-| A postgresql connection options type and related functions. -}  module Database.PostgreSQL.Simple.Options   ( Options(..)   , defaultOptions-  , toArgs   , toConnectionString+  , parseConnectionString   ) where--import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BSC import Data.Maybe (Maybe, maybeToList) import Data.Typeable (Typeable) import GHC.Generics (Generic)+import Data.Monoid.Generic+import Data.List (intercalate)+import Data.List.Split (splitOn)+import Text.Read (readMaybe)+import URI.ByteString as URI+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BSC+import Data.Monoid+import Control.Monad ((<=<), foldM)+import Control.Applicative +-- | A postgresql connection options type. data Options = Options-  { oHost                    :: Maybe String-  , oHostaddr                :: Maybe String-  , oPort                    :: Maybe Int-  , oUser                    :: Maybe String-  , oPassword                :: Maybe String-  , oDbname                  :: String-  , oConnectTimeout          :: Maybe Int-  , oClientEncoding          :: Maybe String-  , oOptions                 :: Maybe String-  , oFallbackApplicationName :: Maybe String-  , oKeepalives              :: Maybe Int-  , oKeepalivesIdle          :: Maybe Int-  , oKeepalivesCount         :: Maybe Int-  , oSslmode                 :: Maybe String-  , oRequiressl              :: Maybe Int-  , oSslcompression          :: Maybe Int-  , oSslcert                 :: Maybe String-  , oSslkey                  :: Maybe String-  , oSslrootcert             :: Maybe String-  , oRequirepeer             :: Maybe String-  , oKrbsrvname              :: Maybe String-  , oGsslib                  :: Maybe String-  , oService                 :: Maybe String-  } deriving (Show, Eq, Read, Ord, Generic, Typeable)--toArgs :: Options -> [String]-toArgs Options {..} =-  [ "--dbname=" <> oDbname-  ]-  ++ (("--host=" <>) <$> maybeToList oHost)-  ++ (("--username=" <>) <$> maybeToList oUser)-  ++ (("--password=" <>) <$> maybeToList oPassword)-  ++ ((\x -> "--host=" <> show x) <$> maybeToList oPort)+  { host                    :: Last String+  , hostaddr                :: Last String+  , port                    :: Last Int+  , user                    :: Last String+  , password                :: Last String+  , dbname                  :: Last String+  , connectTimeout          :: Last Int+  , clientEncoding          :: Last String+  , options                 :: Last String+  , fallbackApplicationName :: Last String+  , keepalives              :: Last Int+  , keepalivesIdle          :: Last Int+  , keepalivesCount         :: Last Int+  , sslmode                 :: Last String+  , requiressl              :: Last Int+  , sslcompression          :: Last Int+  , sslcert                 :: Last String+  , sslkey                  :: Last String+  , sslrootcert             :: Last String+  , requirepeer             :: Last String+  , krbsrvname              :: Last String+  , gsslib                  :: Last String+  , service                 :: Last String+  } deriving stock (Show, Eq, Read, Ord, Generic, Typeable)+    deriving Semigroup via GenericSemigroup Options+    deriving Monoid    via GenericMonoid Options +-- | Make a key value postgresql option string. toConnectionString :: Options -> ByteString toConnectionString Options {..} = BSC.pack $ unwords $ map (\(k, v) -> k <> "=" <> v)-  $  maybeToPairStr "host" oHost-  <> maybeToPairStr "hostaddr" oHostaddr-  <> [ ("dbname", oDbname)-     ]-  <> maybeToPair "port" oPort-  <> maybeToPairStr "password" oPassword-  <> maybeToPairStr "user" oUser-  <> maybeToPair "connect_timeout" oConnectTimeout-  <> maybeToPairStr "client_encoding" oClientEncoding-  <> maybeToPairStr "options" oOptions-  <> maybeToPairStr "fallback_applicationName" oFallbackApplicationName-  <> maybeToPair "keepalives" oKeepalives-  <> maybeToPair "keepalives_idle" oKeepalivesIdle-  <> maybeToPair "keepalives_count" oKeepalivesCount-  <> maybeToPairStr "sslmode" oSslmode-  <> maybeToPair "requiressl" oRequiressl-  <> maybeToPair "sslcompression" oSslcompression-  <> maybeToPairStr "sslcert" oSslcert-  <> maybeToPairStr "sslkey" oSslkey-  <> maybeToPairStr "sslrootcert" oSslrootcert-  <> maybeToPairStr "requirepeer" oRequirepeer-  <> maybeToPairStr "krbsrvname" oKrbsrvname-  <> maybeToPairStr "gsslib" oGsslib-  <> maybeToPairStr "service" oService+  $  maybeToPairStr "host" host+  <> maybeToPairStr "hostaddr" hostaddr+  <> maybeToPairStr "dbname" dbname+  <> maybeToPair "port" port+  <> maybeToPairStr "password" password+  <> maybeToPairStr "user" user+  <> maybeToPair "connect_timeout" connectTimeout+  <> maybeToPairStr "client_encoding" clientEncoding+  <> maybeToPairStr "options" options+  <> maybeToPairStr "fallback_applicationName" fallbackApplicationName+  <> maybeToPair "keepalives" keepalives+  <> maybeToPair "keepalives_idle" keepalivesIdle+  <> maybeToPair "keepalives_count" keepalivesCount+  <> maybeToPairStr "sslmode" sslmode+  <> maybeToPair "requiressl" requiressl+  <> maybeToPair "sslcompression" sslcompression+  <> maybeToPairStr "sslcert" sslcert+  <> maybeToPairStr "sslkey" sslkey+  <> maybeToPairStr "sslrootcert" sslrootcert+  <> maybeToPairStr "requirepeer" requirepeer+  <> maybeToPairStr "krbsrvname" krbsrvname+  <> maybeToPairStr "gsslib" gsslib+  <> maybeToPairStr "service" service   where-  maybeToPairStr :: String -> Maybe String -> [(String, String)]-  maybeToPairStr k mv = (k,) <$> maybeToList mv+  maybeToPairStr :: String -> Last String -> [(String, String)]+  maybeToPairStr k mv = (k,) <$> maybeToList (getLast mv) -  maybeToPair :: Show a => String -> Maybe a -> [(String, String)]-  maybeToPair k mv = (\v -> (k, show v)) <$> maybeToList mv+  maybeToPair :: Show a => String -> Last a -> [(String, String)]+  maybeToPair k mv = (\v -> (k, show v)) <$> maybeToList (getLast mv) -defaultOptions :: String -> Options-defaultOptions dbName = Options {-    oHost                    = Nothing-  , oHostaddr                = Nothing-  , oPort                    = Nothing-  , oUser                    = Nothing-  , oPassword                = Nothing-  , oDbname                  = dbName-  , oConnectTimeout          = Nothing-  , oClientEncoding          = Nothing-  , oOptions                 = Nothing-  , oFallbackApplicationName = Nothing-  , oKeepalives              = Nothing-  , oKeepalivesIdle          = Nothing-  , oKeepalivesCount         = Nothing-  , oSslmode                 = Nothing-  , oRequiressl              = Nothing-  , oSslcompression          = Nothing-  , oSslcert                 = Nothing-  , oSslkey                  = Nothing-  , oSslrootcert             = Nothing-  , oRequirepeer             = Nothing-  , oKrbsrvname              = Nothing-  , oGsslib                  = Nothing-  , oService                 = Nothing-}+{-| Default options.++ @+   defaultOptions :: Options+   defaultOptions = mempty+    { host     = pure "localhost"+    , port     = pure 5432+    , user     = pure "postgres"+    , dbname   = pure "postgres"+    }+ @+-}+defaultOptions :: Options+defaultOptions = mempty+  { host     = pure "localhost"+  , port     = pure 5432+  , user     = pure "postgres"+  , dbname   = pure "postgres"+  }++userInfoToptions :: UserInfo -> Options+userInfoToptions UserInfo {..} = mempty { user = return $ BSC.unpack uiUsername } <> if BS.null uiPassword+  then mempty+  else mempty { password = return $ BSC.unpack uiPassword }++authorityToOptions :: Authority -> Options+authorityToOptions Authority {..} = maybe mempty userInfoToptions authorityUserInfo <>+  mempty { host = return $ BSC.unpack $ hostBS authorityHost } <>+  maybe mempty (\p -> mempty { port = return $ portNumber p }) authorityPort++pathToptions :: ByteString -> Options+pathToptions path = case drop 1 $ BSC.unpack path of+  "" -> mempty+  x  -> mempty {dbname = return x }++parseInt :: String -> String -> Either String Int+parseInt msg v = maybe (Left (msg <> " value of: " <> v <> " is not a number")) Right $+      readMaybe v++parseString :: String -> Maybe String+parseString x = readMaybe x <|> unSingleQuote x <|> Just x++unSingleQuote :: String -> Maybe String+unSingleQuote (x : xs@(_ : _))+  | x == '\'' && last xs == '\'' = Just $ init xs+  | otherwise                    = Nothing+unSingleQuote _                  = Nothing++keywordToptions :: String -> String -> Either String Options+keywordToptions k v = case k of+  "host" -> return $ mempty { host = return v }+  "hostaddress" -> return $ mempty { hostaddr = return v }+  "port" -> do+    portValue <- parseInt "port" v+    return $ mempty { port = return portValue }+  "user" -> return $ mempty { user = return v }+  "password" -> return $ mempty { password = return v }+  "dbname" -> return $ mempty { dbname = return v}+  "connect_timeout" -> do+    x <- parseInt "connect_timeout" v+    return $ mempty { connectTimeout = return x }+  "client_encoding" -> return $ mempty { clientEncoding = return v }+  "options" -> return $ mempty { options = return v }+  "fallback_applicationName" -> return $ mempty { fallbackApplicationName = return v }+  "keepalives" -> do+    x <- parseInt "keepalives" v+    return $ mempty { keepalives = return x }+  "keepalives_idle" -> do+    x <- parseInt "keepalives_idle" v+    return $ mempty { keepalivesIdle = return x }+  "keepalives_count" -> do+    x <- parseInt "keepalives_count" v+    return $ mempty { keepalivesCount = return x }+  "sslmode" -> return $ mempty { sslmode = return v }+  "requiressl" -> do+    x <- parseInt "requiressl" v+    return $ mempty { requiressl = return x }+  "sslcompression" -> do+    x <- parseInt "sslcompression" v+    return $ mempty { sslcompression = return x }+  "sslcert" -> return $ mempty { sslcert = return v }+  "sslkey" -> return $ mempty { sslkey = return v }+  "sslrootcert" -> return $ mempty { sslrootcert = return v }+  "requirepeer" -> return $ mempty { requirepeer = return v }+  "krbsrvname" -> return $ mempty { krbsrvname = return v }+  "gsslib" -> return $ mempty { gsslib = return v }+  "service" -> return $ mempty { service = return v }++  x -> Left $ "Unrecongnized option: " ++ show x++queryToptions :: URI.Query -> Either String Options+queryToptions Query {..} = foldM (\acc (k, v) -> fmap (mappend acc) $ keywordToptions (BSC.unpack k) $ BSC.unpack v) mempty queryPairs++uriToptions :: URIRef Absolute -> Either String Options+uriToptions URI {..} = case schemeBS uriScheme of+  "postgresql" -> do+    queryParts <- queryToptions uriQuery+    return $ maybe mempty authorityToOptions uriAuthority <>+      pathToptions uriPath <> queryParts++  x -> Left $ "Wrong protocol. Expected \"postgresql\" but got: " ++ show x++parseURIStr :: String -> Either String (URIRef Absolute)+parseURIStr = left show . parseURI strictURIParserOptions . BSC.pack where+  left f = \case+    Left x -> Left $ f x+    Right x -> Right x++parseKeywords :: String -> Either String Options+parseKeywords [] = Left "Failed to parse keywords"+parseKeywords x = fmap mconcat . mapM (uncurry keywordToptions <=< toTuple . splitOn "=") $ words x where+  toTuple [k, v] = return (k, v)+  toTuple xs = Left $ "invalid opts:" ++ show (intercalate "=" xs)++-- | Parse a connection string. Can be in URI or keyword format.+parseConnectionString :: String -> Either String Options+parseConnectionString url = do+  url' <- maybe (Left "failed to parse as string") Right $ parseString url+  parseKeywords url' <|> (uriToptions =<< parseURIStr url')
+ test/Spec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings, CPP #-}+import Test.Hspec+import Database.PostgreSQL.Simple.Options++main :: IO ()+main = hspec $ describe "connection string parser" $ do+  it "fails on empty" $ parseConnectionString "" `shouldBe` Left "MalformedScheme NonAlphaLeading"++  it "parses a single keyword" $ parseConnectionString "host=localhost"+    `shouldBe` Right (mempty { host = return "localhost" })++  it "parses all keywords" $ parseConnectionString+    "host=localhost port=1234 user=jonathan password=open dbname=dev" `shouldBe`+      Right (mempty+        { host = return "localhost"+        , port = return 1234+        , user = return "jonathan"+        , password = return "open"+        , dbname = return "dev"+        })++  it "parses host only connection string" $ parseConnectionString "postgresql://localhost"+    `shouldBe` Right (mempty { host = return "localhost" })++  it "parses all params" $ parseConnectionString "postgresql://jonathan:open@localhost:1234/dev"+    `shouldBe` Right (mempty+        { host = return "localhost"+        , port = return 1234+        , user = return "jonathan"+        , password = return "open"+        , dbname = return "dev"+        })++  it "parses all params using query params" $ parseConnectionString "postgresql:///dev?host=localhost&port=1234"+    `shouldBe` Right (mempty+      { host = return "localhost"+      , port = return 1234+      , dbname = return "dev"+      })++  it "decodes a unix port" $ parseConnectionString "postgresql://%2Fvar%2Flib%2Fpostgresql/dbname"+    `shouldBe` Right (mempty+        { host = return "/var/lib/postgresql"+        , dbname = return "dbname"+        })