-----------------------------------------------------------------------------
-- |
-- Module      :  Database.PostgreSQL.Simple.URL
-- Copyright   :  2014-2018 © Futurice OY, Oleg Grenrus
-- License     :  MIT (see the file LICENSE)
--
-- Maintainer  :  Oleg Grenrus <[email protected]>
--
----------------------------------------------------------------------------
module Database.PostgreSQL.Simple.URL (parseDatabaseUrl, uriToConnectInfo) where

import Control.Applicative
import Data.List.Split
import Database.PostgreSQL.Simple
import Network.URI
import Prelude

-- | Parse string url into `ConnectInfo`.
--
-- >>> parseDatabaseUrl "postgres://foo:[email protected]:2345/database"
-- Just (ConnectInfo {connectHost = "example.com", connectPort = 2345, connectUser = "foo", connectPassword = "bar", connectDatabase = "database"})
--
-- >>> parseDatabaseUrl "postgresql://foo:[email protected]:2345/database"
-- Just (ConnectInfo {connectHost = "example.com", connectPort = 2345, connectUser = "foo", connectPassword = "bar", connectDatabase = "database"})
--
parseDatabaseUrl :: String -> Maybe ConnectInfo
parseDatabaseUrl :: String -> Maybe ConnectInfo
parseDatabaseUrl String
databaseUrl = String -> Maybe URI
parseURI String
databaseUrl Maybe URI -> (URI -> Maybe ConnectInfo) -> Maybe ConnectInfo
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= URI -> Maybe ConnectInfo
uriToConnectInfo

uriToConnectInfo :: URI -> Maybe ConnectInfo
uriToConnectInfo :: URI -> Maybe ConnectInfo
uriToConnectInfo URI
uri
  | URI -> String
uriScheme URI
uri String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"postgres:" Bool -> Bool -> Bool
&& URI -> String
uriScheme URI
uri String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"postgresql:" = Maybe ConnectInfo
forall a. Maybe a
Nothing
  | Bool
otherwise = ((ConnectInfo -> ConnectInfo) -> ConnectInfo -> ConnectInfo
forall a b. (a -> b) -> a -> b
$ ConnectInfo
defaultConnectInfo) ((ConnectInfo -> ConnectInfo) -> ConnectInfo)
-> Maybe (ConnectInfo -> ConnectInfo) -> Maybe ConnectInfo
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> URI -> Maybe (ConnectInfo -> ConnectInfo)
mkConnectInfo URI
uri

type ConnectInfoChange = ConnectInfo -> ConnectInfo

mkConnectInfo :: URI -> Maybe ConnectInfoChange
mkConnectInfo :: URI -> Maybe (ConnectInfo -> ConnectInfo)
mkConnectInfo URI
uri = case URI -> String
uriPath URI
uri of
                           (Char
'/' : String
rest) | Bool -> Bool
not (String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
rest) -> (ConnectInfo -> ConnectInfo) -> Maybe (ConnectInfo -> ConnectInfo)
forall a. a -> Maybe a
Just ((ConnectInfo -> ConnectInfo)
 -> Maybe (ConnectInfo -> ConnectInfo))
-> (ConnectInfo -> ConnectInfo)
-> Maybe (ConnectInfo -> ConnectInfo)
forall a b. (a -> b) -> a -> b
$ URI -> ConnectInfo -> ConnectInfo
uriParameters URI
uri
                           String
_                              -> Maybe (ConnectInfo -> ConnectInfo)
forall a. Maybe a
Nothing

uriParameters :: URI -> ConnectInfoChange
uriParameters :: URI -> ConnectInfo -> ConnectInfo
uriParameters URI
uri = (\ConnectInfo
info -> ConnectInfo
info { connectDatabase = tail $ uriPath uri }) (ConnectInfo -> ConnectInfo)
-> (ConnectInfo -> ConnectInfo) -> ConnectInfo -> ConnectInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ConnectInfo -> ConnectInfo)
-> (URIAuth -> ConnectInfo -> ConnectInfo)
-> Maybe URIAuth
-> ConnectInfo
-> ConnectInfo
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ConnectInfo -> ConnectInfo
forall a. a -> a
id URIAuth -> ConnectInfo -> ConnectInfo
uriAuthParameters (URI -> Maybe URIAuth
uriAuthority URI
uri)

dropLast :: [a] -> [a]
dropLast :: forall a. [a] -> [a]
dropLast []     = []
dropLast [a
_]    = []
dropLast (a
x:[a]
xs) = a
x a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a] -> [a]
forall a. [a] -> [a]
dropLast [a]
xs

uriAuthParameters :: URIAuth -> ConnectInfoChange
uriAuthParameters :: URIAuth -> ConnectInfo -> ConnectInfo
uriAuthParameters URIAuth
uriAuth = ConnectInfo -> ConnectInfo
port (ConnectInfo -> ConnectInfo)
-> (ConnectInfo -> ConnectInfo) -> ConnectInfo -> ConnectInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ConnectInfo -> ConnectInfo
host (ConnectInfo -> ConnectInfo)
-> (ConnectInfo -> ConnectInfo) -> ConnectInfo -> ConnectInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ConnectInfo -> ConnectInfo
auth
  where port :: ConnectInfo -> ConnectInfo
port = case URIAuth -> String
uriPort URIAuth
uriAuth of
                 (Char
':' : String
p) -> \ConnectInfo
info -> ConnectInfo
info { connectPort = read p }
                 String
_         -> ConnectInfo -> ConnectInfo
forall a. a -> a
id
        host :: ConnectInfo -> ConnectInfo
host = case URIAuth -> String
uriRegName URIAuth
uriAuth of
                 String
h  -> \ConnectInfo
info -> ConnectInfo
info { connectHost = h }
        auth :: ConnectInfo -> ConnectInfo
auth = case String -> String -> [String]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn String
":" (URIAuth -> String
uriUserInfo URIAuth
uriAuth) of
                 [String
""]   -> ConnectInfo -> ConnectInfo
forall a. a -> a
id
                 [String
u]    -> \ConnectInfo
info -> ConnectInfo
info { connectUser = dropLast u }
                 [String
u, String
p] -> \ConnectInfo
info -> ConnectInfo
info { connectUser = u, connectPassword = dropLast p }
                 [String]
_      -> ConnectInfo -> ConnectInfo
forall a. a -> a
id