Advertisement
Guest User

Untitled

a guest
Oct 11th, 2016
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.67 KB | None | 0 0
  1. module URIToConnect (connectInfo,
  2. connectInfoToURI,
  3. uri,
  4. uriToConnectInfo) where
  5. import Control.Applicative ((<|>), empty)
  6. import Control.Lens hiding (noneOf, uncons) -- from: lens
  7. import Control.Monad (replicateM)
  8. import Data.ByteString.Base16.Lazy (decode) -- from: base16-bytestring
  9. import Data.ByteString.Builder as BSB -- from: bytestring
  10. import Data.ByteString.Lazy (ByteString) -- from: bytestring
  11. import qualified Data.ByteString.Lazy as BS -- from: bytestring
  12. import Data.ByteString.Lazy.Char8 (uncons, unpack) -- from: bytestring
  13. import Data.Foldable (find)
  14. import Data.Monoid ((<>), Any(..))
  15. import Data.Word (Word16)
  16. import Database.PostgreSQL.Simple (ConnectInfo(..),
  17. defaultConnectInfo) -- from: postgresql-simple
  18. import Network.URI hiding (unreserved) -- from: network-uri
  19. import Text.Parsec (many, many1, digit, char,
  20. eof, option, parse, satisfy) -- from: parsec
  21. import Text.Parsec.Char (hexDigit, oneOf) -- from: parsec
  22. import Text.Parsec.String (Parser) -- from: parsec
  23.  
  24. {-
  25.  
  26. λ> postgreSQLConnectionString <$> "postgresql://scott:tiger@localhost/mydatabase" ^? uri . connectInfo
  27. Just "host='localhost' port=5432 user='scott' password='tiger' dbname='mydatabase'"
  28.  
  29. λ> review connectInfo <$> "postgresql://scott:tiger@localhost/mydatabase" ^? uri . connectInfo
  30. Just postgresql://scott:...@localhost:5432/mydatabase
  31.  
  32. -}
  33.  
  34. newtype DBName = DBName { getDBName :: String } deriving (Eq, Show)
  35.  
  36. data URIError = UnknownScheme String
  37. | NoAuthInfo
  38. | NoDatabase -- not used
  39. deriving (Eq, Show)
  40.  
  41. -- conversion
  42.  
  43. uriToConnectInfo :: URI -> Either URIError ConnectInfo
  44. uriToConnectInfo u = do
  45. case u ^. _uriScheme of
  46. "postgresql:" -> return ()
  47. unknown -> Left $ UnknownScheme unknown
  48. uauth <- maybe (Left NoAuthInfo) Right $ u ^. _uriAuthority
  49. return $ defaultConnectInfo & _connectHost .~* uauth ^. _uriRegName
  50. & _connectPort .~? uauth ^? _uriPort . port
  51. & _connectUser .~? uauth ^? _uriUserInfo . unpw . _1
  52. & _connectPassword .~? uauth ^? _uriUserInfo . unpw . _2
  53. & _connectDatabase .~? u ^? _uriPath . dbName . coerced
  54.  
  55. connectInfoToURI :: ConnectInfo -> URI
  56. connectInfoToURI cinfo =
  57. let cred = (cinfo ^. _connectUser, cinfo ^. _connectPassword) ^. re unpw
  58. auth = emptyURIAuth & _uriUserInfo .~ cred
  59. & _uriRegName .~ (cinfo ^. _connectHost)
  60. & _uriPort .~ (cinfo ^. _connectPort . re port)
  61. in emptyURI & _uriScheme .~ "postgresql:"
  62. & _uriAuthority ?~ auth
  63. & _uriPath .~ (cinfo ^. _connectDatabase . coerced . re dbName)
  64.  
  65. -- prisms
  66.  
  67. connectInfo :: Prism' URI ConnectInfo
  68. connectInfo = prism' connectInfoToURI $ either (const Nothing) Just . uriToConnectInfo
  69.  
  70. uri :: Prism' String URI
  71. uri = prism' p parseURI
  72. where p s = uriToString id s ""
  73.  
  74. -- internal prisms
  75.  
  76. port :: Prism' String Word16
  77. port = prism' q p
  78. where q i = ':' : show i
  79. p = either (const Nothing) Just . parse (parsePort <* eof) ""
  80.  
  81. dbName :: Prism' String DBName
  82. dbName = prism' q p
  83. where p = either (const Nothing) Just . parse (parseDBName <* eof) ""
  84. q (DBName nm) = buildString $ charUtf8 '/' <> encodeChars toPctEncoded [
  85. isUnreserved, isSubDelims, (`elem` (":@" :: String))] nm
  86.  
  87. unpw :: Prism' String (String, String)
  88. unpw = prism' q p
  89. where q (a, b) = buildString $ f a <> r b <> charUtf8 '@'
  90. f = encodeChars toPctEncoded [isUnreserved, isSubDelims]
  91. g = encodeChars toPctEncoded [isUnreserved, isSubDelims, (== ':')]
  92. p = either (const Nothing) Just . parse (parseUNPW <* eof) ""
  93. r s | null s = mempty
  94. | otherwise = charUtf8 ':' <> g s
  95.  
  96. -- empty values for creating new URI values w/ lenses
  97.  
  98. emptyURI :: URI
  99. emptyURI = URI "" Nothing "" "" ""
  100.  
  101. emptyURIAuth :: URIAuth
  102. emptyURIAuth = URIAuth "" "" ""
  103.  
  104. -- some new lens operators for dealing with empty values
  105.  
  106. (.~?) :: ASetter' s a -> Maybe a -> s -> s
  107. _ .~? Nothing = id
  108. f .~? Just x = f .~ x
  109. infixr 4 .~?
  110.  
  111. (.~*) :: (Eq a, Monoid a) => ASetter' s a -> a -> s -> s
  112. f .~* a | a == mempty = id
  113. | otherwise = f .~ a
  114. infixr 4 .~*
  115.  
  116. -- parsing strings
  117.  
  118. parsePort :: Parser Word16
  119. parsePort = char ':' *> (read <$> many1 digit)
  120.  
  121. parseUNPW :: Parser (String, String)
  122. parseUNPW = go <* char '@'
  123. where go = (,) <$> many authpart <*> option "" pw
  124. pw = char ':' *> many authpart'
  125. authpart = unreserved <|> pctEncoded
  126. authpart' = authpart <|> char ':'
  127.  
  128. unreserved :: Parser Char
  129. unreserved = satisfy isUnreserved
  130.  
  131. pctEncoded :: Parser Char
  132. pctEncoded = let x = char '%' *> replicateM 2 hexDigit
  133. y = fmap fst . (>>= uncons) . filterPartialDecode . decode . BSB.toLazyByteString . BSB.stringUtf8 <$> x
  134. in y >>= maybe empty return
  135. where filterPartialDecode :: (a, ByteString) -> Maybe a
  136. filterPartialDecode s = const (fst s) <$> find BS.null s
  137.  
  138. subDelimChars :: String -- helps with overloaded strings and elem
  139. subDelimChars = "!$&'()*+,;="
  140.  
  141. subDelims :: Parser Char
  142. subDelims = oneOf subDelimChars
  143.  
  144. isSubDelims :: Char -> Bool
  145. isSubDelims c = c `elem` subDelimChars
  146.  
  147.  
  148. pChar :: Parser Char
  149. pChar = unreserved <|> pctEncoded <|> subDelims <|> oneOf ":@"
  150.  
  151. parseDBName :: Parser DBName
  152. parseDBName = char '/' *> (DBName <$> many1 pChar)
  153.  
  154. -- building strings
  155.  
  156. buildString :: Builder -> String
  157. buildString = unpack . BSB.toLazyByteString
  158.  
  159. toPctEncoded :: Char -> Builder
  160. toPctEncoded = pctEveryByte . lazify . builder
  161. where builder = BSB.lazyByteStringHex . lazify . BSB.charUtf8
  162. lazify = BSB.toLazyByteString
  163.  
  164. encodeChars :: (Char -> Builder) -> [Char -> Bool] -> String -> Builder
  165. encodeChars f preds = foldr step mempty
  166. where p = fmap getAny . mconcat $ fmap (fmap Any) preds
  167. step c r = if p c then charUtf8 c <> r else f c <> r
  168.  
  169. pctEveryByte :: ByteString -> Builder
  170. pctEveryByte s = let (a, b) = BS.splitAt 2 s
  171. in if BS.null a
  172. then mempty
  173. else BSB.charUtf8 '%' <> BSB.lazyByteString a <> pctEveryByte b
  174.  
  175. --- let's make some lenses for our external types
  176. --- can probably do these with template haskell and some options
  177.  
  178. _uriScheme :: Lens' URI String
  179. _uriScheme = lens uriScheme $ \s r -> s { uriScheme = r }
  180.  
  181. _uriAuthority :: Lens' URI (Maybe URIAuth)
  182. _uriAuthority = lens uriAuthority $ \s r -> s { uriAuthority = r }
  183.  
  184. _uriPath :: Lens' URI String
  185. _uriPath = lens uriPath $ \s r -> s { uriPath = r }
  186.  
  187. _uriQuery :: Lens' URI String
  188. _uriQuery = lens uriQuery $ \s r -> s { uriQuery = r }
  189.  
  190. _uriFragment :: Lens' URI String
  191. _uriFragment = lens uriFragment $ \s r -> s { uriFragment = r }
  192.  
  193. _uriUserInfo :: Lens' URIAuth String
  194. _uriUserInfo = lens uriUserInfo $ \s r -> s { uriUserInfo = r }
  195.  
  196. _uriRegName :: Lens' URIAuth String
  197. _uriRegName = lens uriRegName $ \s r -> s { uriRegName = r }
  198.  
  199. _uriPort :: Lens' URIAuth String
  200. _uriPort = lens uriPort $ \s r -> s { uriPort = r }
  201.  
  202. _connectHost :: Lens' ConnectInfo String
  203. _connectHost = lens connectHost $ \s r -> s { connectHost = r }
  204.  
  205. _connectPort :: Lens' ConnectInfo Word16
  206. _connectPort = lens connectPort $ \s r -> s { connectPort = r }
  207.  
  208. _connectUser :: Lens' ConnectInfo String
  209. _connectUser = lens connectUser $ \s r -> s { connectUser = r }
  210.  
  211. _connectPassword :: Lens' ConnectInfo String
  212. _connectPassword = lens connectPassword $ \s r -> s { connectPassword = r }
  213.  
  214. _connectDatabase :: Lens' ConnectInfo String
  215. _connectDatabase = lens connectDatabase $ \s r -> s { connectDatabase = r }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement