Protolude second movement (#677)

* Replace Prelude import for Protolude in middleware

* Remove qualifier from Text type

* Replace Prelude for Protolude, replace 'cs' for 'toS' calls, also change some name bindings

* Replace Prelude in main for Protolude. Replace error calls for panic calls. Also replace cs for toS

* Replace Prelude for Protolude

* Replace Prelude for Protolude in OpenAPI
This commit is contained in:
Diogo Biazus
2016-07-23 12:20:36 -07:00
committed by Joe Nelson
parent d93d07795b
commit df6cbc4afa
6 changed files with 91 additions and 100 deletions
+14 -21
View File
@@ -2,7 +2,7 @@
module Main where
import Prelude
import Protolude
import PostgREST.App
import PostgREST.Config (AppConfig (..),
minimumPgVersion,
@@ -11,10 +11,8 @@ import PostgREST.Config (AppConfig (..),
import PostgREST.OpenAPI (isMalformedProxyUri)
import PostgREST.DbStructure
import Control.Monad
import Data.Monoid ((<>))
import Data.String.Conversions (cs)
import Data.String (IsString (..))
import Data.Function (id)
import qualified Hasql.Query as H
import qualified Hasql.Session as H
import qualified Hasql.Decoders as HD
@@ -22,25 +20,21 @@ import qualified Hasql.Encoders as HE
import qualified Hasql.Pool as P
import Network.Wai.Handler.Warp
import System.IO (BufferMode (..),
hSetBuffering, stderr,
stdin, stdout)
hSetBuffering)
import Web.JWT (secret)
import Data.IORef
#ifndef mingw32_HOST_OS
import Control.Monad.IO.Class (liftIO)
import System.Posix.Signals
import Control.Concurrent (myThreadId)
import Control.Exception.Base (throwTo, AsyncException(..))
#endif
isServerVersionSupported :: H.Session Bool
isServerVersionSupported = do
ver <- H.query () pgVersion
return $ read (cs ver) >= minimumPgVersion
return $ toInteger ver >= minimumPgVersion
where
pgVersion =
H.statement "SHOW server_version_num"
HE.unit (HD.singleRow $ HD.value HD.text) True
HE.unit (HD.singleRow $ HD.value HD.int4) True
main :: IO ()
main = do
@@ -52,30 +46,29 @@ main = do
let host = configHost conf
port = configPort conf
proxy = configProxyUri conf
pgSettings = cs (configDatabase conf)
pgSettings = toS (configDatabase conf)
appSettings = setHost (fromString host)
. setPort port
. setServerName (cs $ "postgrest/" <> prettyVersion)
. setServerName (toS $ "postgrest/" <> prettyVersion)
$ defaultSettings
when (isMalformedProxyUri proxy) $ error
when (isMalformedProxyUri $ toS <$> proxy) $ panic
"Malformed proxy uri, a correct example: https://example.com:8443/basePath"
unless (secret "secret" /= configJwtSecret conf) $
putStrLn "WARNING, running in insecure mode, JWT secret is the default value"
Prelude.putStrLn $ "Listening on port " ++
(show $ configPort conf :: String)
putStrLn ("WARNING, running in insecure mode, JWT secret is the default value" :: Text)
putStrLn $ ("Listening on port " :: Text) <> show (configPort conf)
pool <- P.acquire (configPool conf, 10, pgSettings)
result <- P.use pool $ do
supported <- isServerVersionSupported
unless supported $ error (
unless supported $ panic (
"Cannot run in this PostgreSQL version, PostgREST needs at least "
<> show minimumPgVersion)
getDbStructure (cs $ configSchema conf)
getDbStructure (toS $ configSchema conf)
refDbStructure <- newIORef $ either (error.show) id result
refDbStructure <- newIORef $ either (panic . show) id result
#ifndef mingw32_HOST_OS
tid <- myThreadId
@@ -87,7 +80,7 @@ main = do
void $ installHandler sigHUP (
Catch . void . P.use pool $ do
s <- getDbStructure (cs $ configSchema conf)
s <- getDbStructure (toS $ configSchema conf)
liftIO $ atomicWriteIORef refDbStructure s
) Nothing
#endif
+42 -43
View File
@@ -7,14 +7,12 @@ module PostgREST.App (
) where
import Control.Applicative
import Data.Bifunctor (first)
import qualified Data.ByteString.Char8 as BS
import Data.IORef (IORef, readIORef)
import Data.List (find, delete)
import Data.Maybe (fromMaybe, fromJust, mapMaybe)
import Data.List (delete, lookup)
import Data.Maybe (fromJust)
import Data.Ranged.Ranges (emptyRange)
import Data.String.Conversions (cs)
import Data.Text (Text, replace, strip, pack, isInfixOf, dropWhile, drop)
import Data.Text (replace, strip, pack, isInfixOf, dropWhile, drop)
import Data.Tree
import qualified Hasql.Pool as P
@@ -62,8 +60,9 @@ import PostgREST.QueryBuilder ( callProc
import PostgREST.Types
import PostgREST.OpenAPI
import Prelude hiding (dropWhile, drop)
import Data.Foldable (foldr1)
import Data.Function (id)
import Protolude hiding (dropWhile, drop, Proxy)
postgrest :: AppConfig -> IORef DbStructure -> P.Pool -> Application
postgrest conf refDbStructure pool =
@@ -74,7 +73,7 @@ postgrest conf refDbStructure pool =
body <- strictRequestBody req
dbStructure <- readIORef refDbStructure
let schema = cs $ configSchema conf
let schema = toS $ configSchema conf
apiRequest = userApiRequest schema req body
eClaims = jwtClaims (configJwtSecret conf) (iJWT apiRequest) time
authed = containsRole eClaims
@@ -95,13 +94,13 @@ app dbStructure conf apiRequest =
let
-- TODO: blow up for Left values (there is a middleware that checks the headers)
contentType = either (const ApplicationJSON) id (iAccepts apiRequest)
contentTypeH = (hContentType, cs $ show contentType) in
contentTypeH = (hContentType, show contentType) in
case (iAction apiRequest, iTarget apiRequest, iPayload apiRequest) of
(ActionRead, TargetIdent qi, Nothing) ->
case readSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ cs e
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (q, cq) -> do
let singular = iPreferSingular apiRequest
stm = createReadStatement q cq singular
@@ -112,22 +111,22 @@ app dbStructure conf apiRequest =
if singular
then return $ if queryTotal <= 0
then responseLBS status404 [] ""
else responseLBS status200 [contentTypeH] (cs body)
else responseLBS status200 [contentTypeH] (toS body)
else do
let (status, contentRange) = rangeHeader queryTotal tableTotal
canonical = iCanonicalQS apiRequest
return $ responseLBS status
[contentTypeH, contentRange,
("Content-Location",
"/" <> cs (qiName qi) <>
if Prelude.null canonical then "" else "?" <> cs canonical
"/" <> toS (qiName qi) <>
if Protolude.null canonical then "" else "?" <> toS canonical
)
] (cs body)
] (toS body)
(ActionCreate, TargetIdent qi@(QualifiedIdentifier _ table),
Just payload@(PayloadJSON uniform@(UniformObjects rows))) ->
case mutateSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ cs e
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (sq,mq) -> do
let isSingle = (==1) $ V.length rows
let pKeys = map pkName $ filter (filterPk schema table) allPrKeys -- would it be ok to move primary key detection in the query itself?
@@ -136,15 +135,15 @@ app dbStructure conf apiRequest =
let (_, _, fs, body) = extractQueryResult row
header =
if null fs then []
else [(hLocation, "/" <> cs table <> renderLocationFields fs)]
else [(hLocation, "/" <> toS table <> renderLocationFields fs)]
return $ if iPreferRepresentation apiRequest == Full
then responseLBS status201 (contentTypeH : header) (cs body)
then responseLBS status201 (contentTypeH : header) (toS body)
else responseLBS status201 header ""
(ActionUpdate, TargetIdent qi, Just payload@(PayloadJSON uniform)) ->
case mutateSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ cs e
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (sq,mq) -> do
let stm = createWriteStatement qi sq mq False (iPreferRepresentation apiRequest) [] (contentType == TextCSV) payload
row <- H.query uniform stm
@@ -154,12 +153,12 @@ app dbStructure conf apiRequest =
| iPreferRepresentation apiRequest == Full -> status200
| otherwise -> status204
return $ if iPreferRepresentation apiRequest == Full
then responseLBS s [contentTypeH, r] (cs body)
then responseLBS s [contentTypeH, r] (toS body)
else responseLBS s [r] ""
(ActionDelete, TargetIdent qi, Nothing) ->
case mutateSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ cs e
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (sq,mq) -> do
let emptyUniform = UniformObjects V.empty
fakeload = PayloadJSON emptyUniform
@@ -170,7 +169,7 @@ app dbStructure conf apiRequest =
return $ if queryTotal == 0
then notFound
else if iPreferRepresentation apiRequest == Full
then responseLBS status200 [contentTypeH, r] (cs body)
then responseLBS status200 [contentTypeH, r] (toS body)
else responseLBS status204 [r] ""
(ActionInfo, TargetIdent (QualifiedIdentifier tSchema tTable), Nothing) ->
@@ -182,7 +181,7 @@ app dbStructure conf apiRequest =
pkeys = map pkName $ filter (filterPk tSchema tTable) allPrKeys
body = encode (TableOptions cols pkeys)
acceptH = (hAllow, if tableInsertable table then "GET,POST,PATCH,DELETE" else "GET") in
return $ responseLBS status200 [jsonH, allOrigins, acceptH] $ cs body
return $ responseLBS status200 [jsonH, allOrigins, acceptH] $ toS body
(ActionInvoke, TargetProc qi,
Just (PayloadJSON (UniformObjects payload))) -> do
@@ -192,7 +191,7 @@ app dbStructure conf apiRequest =
returnType = lookup (qiName qi) $ dbProcs dbStructure
returnsJWT = fromMaybe False $ isInfixOf "jwt_claims" <$> returnType
case readSqlParts of
Left e -> return $ responseLBS status400 [jsonH] $ cs e
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (q,cq) -> respondToRange $ do
row <- H.query () (callProc qi p q cq topLevelRange shouldCount singular)
let (tableTotal, queryTotal, body) = fromMaybe (Just 0, 0, emptyArray) row
@@ -200,27 +199,27 @@ app dbStructure conf apiRequest =
in
return $ responseLBS status [jsonH, contentRange]
(if returnsJWT
then "{\"token\":\"" <> cs (tokenJWT jwtSecret body) <> "\"}"
else cs $ encode body)
then "{\"token\":\"" <> toS (tokenJWT jwtSecret body) <> "\"}"
else toS $ encode body)
(ActionRead, TargetRoot, Nothing) -> do
let encodeApi ti = encodeOpenAPI ti uri'
host = configHost conf
port = toInteger $ configPort conf
proxy = pickProxy $ configProxyUri conf
proxy = pickProxy $ toS <$> configProxyUri conf
uri Nothing = ("http", pack host, port, "/")
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
uri' = uri proxy
encodeFn = if contentType == OpenAPI then encodeApi . toTableInfo else encode
header = if contentType == OpenAPI then openapiH else jsonH
body <- encodeFn <$> H.query schema accessibleTables
return $ responseLBS status200 [header] $ cs body
return $ responseLBS status200 [header] $ toS body
(ActionInappropriate, _, _) -> return $ responseLBS status405 [] ""
(_, _, Just (PayloadParseError e)) ->
return $ responseLBS status400 [jsonH] $
cs (formatGeneralError "Cannot parse request payload" (cs e))
toS (formatGeneralError "Cannot parse request payload" (toS e))
(_, TargetUnknown _, _) -> return notFound
@@ -242,7 +241,7 @@ app dbStructure conf apiRequest =
filterCol _ _ _ = False
allPrKeys = dbPrimaryKeys dbStructure
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
schema = cs $ configSchema conf
schema = toS $ configSchema conf
shouldCount = iPreferCount apiRequest
topLevelRange = fromMaybe allRange $ M.lookup "limit" $ iRange apiRequest
readDbRequest = DbRead <$> buildReadRequest (configMaxRows conf) (dbRelations dbStructure) (dbProcs dbStructure) apiRequest
@@ -255,10 +254,10 @@ app dbStructure conf apiRequest =
respondToRange response = if topLevelRange == emptyRange
then return $ errResponse status416 "HTTP Range error"
else response
rangeHeader queryTotal tableTotal = let frm = rangeOffset topLevelRange
to = frm + toInteger queryTotal - 1
contentRange = contentRangeH frm to (toInteger <$> tableTotal)
status = rangeStatus frm to (toInteger <$> tableTotal)
rangeHeader queryTotal tableTotal = let lower = rangeOffset topLevelRange
upper = lower + toInteger queryTotal - 1
contentRange = contentRangeH lower upper (toInteger <$> tableTotal)
status = rangeStatus lower upper (toInteger <$> tableTotal)
in (status, contentRange)
splitKeyValue :: BS.ByteString -> (BS.ByteString, BS.ByteString)
@@ -271,22 +270,22 @@ renderLocationFields fields =
rangeStatus :: Integer -> Integer -> Maybe Integer -> Status
rangeStatus _ _ Nothing = status200
rangeStatus frm to (Just total)
| frm > total = status416
| (1 + to - frm) < total = status206
rangeStatus lower upper (Just total)
| lower > total = status416
| (1 + upper - lower) < total = status206
| otherwise = status200
contentRangeH :: Integer -> Integer -> Maybe Integer -> Header
contentRangeH frm to total =
("Content-Range", cs headerValue)
contentRangeH lower upper total =
("Content-Range", headerValue)
where
headerValue = rangeString <> "/" <> totalString
rangeString
| totalNotZero && fromInRange = show frm <> "-" <> cs (show to)
| totalNotZero && fromInRange = show lower <> "-" <> show upper
| otherwise = "*"
totalString = fromMaybe "*" (show <$> total)
totalNotZero = fromMaybe True ((/=) 0 <$> total)
fromInRange = frm <= to
fromInRange = lower <= upper
jsonH :: Header
jsonH = (hContentType, "application/json; charset=utf-8")
@@ -301,12 +300,12 @@ formatRelationError = formatGeneralError
formatParserError :: ParseError -> Text
formatParserError e = formatGeneralError message details
where
message = cs $ show (errorPos e)
details = strip $ replace "\n" " " $ cs
message = show $ errorPos e
details = strip $ replace "\n" " " $ toS
$ showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" (errorMessages e)
formatGeneralError :: Text -> Text -> Text
formatGeneralError message details = cs $ encode $ object [
formatGeneralError message details = toS $ encode $ object [
"message" .= message,
"details" .= details]
+10 -10
View File
@@ -29,21 +29,21 @@ pgErrResponse authed e =
instance JSON.ToJSON P.UsageError where
toJSON (P.ConnectionError e) = JSON.object [
"code" .= ("" :: T.Text),
"message" .= ("Connection error" :: T.Text),
"details" .= (toS $ fromMaybe "" e :: T.Text)]
"code" .= ("" :: Text),
"message" .= ("Connection error" :: Text),
"details" .= (toS $ fromMaybe "" e :: Text)]
toJSON (P.SessionError e) = JSON.toJSON e -- H.Error
instance JSON.ToJSON H.Error where
toJSON (H.ResultError (H.ServerError c m d h)) = JSON.object [
"code" .= (toS c::T.Text),
"message" .= (toS m::T.Text),
"details" .= (fmap toS d::Maybe T.Text),
"hint" .= (fmap toS h::Maybe T.Text)]
"code" .= (toS c::Text),
"message" .= (toS m::Text),
"details" .= (fmap toS d::Maybe Text),
"hint" .= (fmap toS h::Maybe Text)]
toJSON (H.ResultError (H.UnexpectedResult m)) = JSON.object [
"message" .= (m::T.Text)]
"message" .= (m::Text)]
toJSON (H.ResultError (H.RowError i H.EndOfInput)) = JSON.object [
"message" .= ("Row error: end of input"::T.Text),
"message" .= ("Row error: end of input"::Text),
"details" .=
("Attempt to parse more columns than there are in the result"::Text),
"details" .= (("Row number " <> show i)::Text)]
@@ -60,7 +60,7 @@ instance JSON.ToJSON H.Error where
"details" .= i]
toJSON (H.ClientError d) = JSON.object [
"message" .= ("Database client error"::Text),
"details" .= (fmap toS d::Maybe T.Text)]
"details" .= (fmap toS d::Maybe Text)]
httpStatus :: Bool -> P.UsageError -> HT.Status
httpStatus _ (P.ConnectionError _) = HT.status500
+3 -3
View File
@@ -6,8 +6,6 @@ module PostgREST.Middleware where
import Data.Aeson (Value (..))
import qualified Data.HashMap.Strict as M
import Data.String.Conversions (cs)
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Text
import qualified Hasql.Transaction as H
import Network.HTTP.Types.Header (hAccept)
@@ -22,8 +20,10 @@ import PostgREST.ApiRequest (ApiRequest(..), ContentType(..),
import PostgREST.Auth (claimsToSQL)
import PostgREST.Config (AppConfig (..), corsPolicy)
import PostgREST.Error (errResponse)
import Data.Text
import Data.List (lookup)
import Prelude hiding (concat, null)
import Protolude hiding (concat, null)
runWithClaims :: AppConfig -> Either Text (M.HashMap Text Value) ->
(ApiRequest -> H.Transaction Response) ->
+14 -13
View File
@@ -8,16 +8,15 @@ module PostgREST.OpenAPI (
import Control.Lens
import Data.Aeson (decode, encode)
import Data.ByteString.Lazy (ByteString)
import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList)
import Data.Maybe (isJust, isNothing, fromJust)
import Data.Maybe (fromJust)
import Data.String (IsString (..))
import Data.Text (Text, unpack, pack, concat, intercalate, init, tail, toLower)
import Data.Text (unpack, pack, concat, intercalate, init, tail, toLower)
import qualified Data.Set as Set
import Network.URI (parseURI, isAbsoluteURI,
URI (..), URIAuth (..))
import Prelude hiding (concat, init, tail)
import Protolude hiding (concat, (&), Proxy, get, intercalate)
import Data.Swagger
@@ -244,7 +243,7 @@ postgrestSpec ti (s, h, p, b) = (mempty :: Swagger)
s' = if s == "http" then Http else Https
h' = Just $ Host (unpack $ escapeHostName h) (Just (fromInteger p))
encodeOpenAPI :: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> ByteString
encodeOpenAPI :: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> LByteString
encodeOpenAPI ti uri = encode $ postgrestSpec ti uri
{-|
@@ -256,16 +255,16 @@ encodeOpenAPI ti uri = encode $ postgrestSpec ti uri
http://postgrest.com/openapi.json
https://postgrest.com:8080/openapi.json
-}
isMalformedProxyUri :: Maybe String -> Bool
isMalformedProxyUri :: Maybe Text -> Bool
isMalformedProxyUri Nothing = False
isMalformedProxyUri (Just uri)
| isAbsoluteURI uri = not $ isUriValid $ toURI uri
| isAbsoluteURI (toS uri) = not $ isUriValid $ toURI uri
| otherwise = True
toURI :: String -> URI
toURI uri = fromJust $ parseURI uri
toURI :: Text -> URI
toURI uri = fromJust $ parseURI (toS uri)
pickProxy :: Maybe String -> Maybe Proxy
pickProxy :: Maybe Text -> Maybe Proxy
pickProxy proxy
| isNothing proxy = Nothing
-- should never happen
@@ -287,11 +286,12 @@ pickProxy proxy
authority = fromJust $ uriAuthority uri
host' = pack $ uriRegName authority
port' = uriPort authority
readPort = fromMaybe 80 . readMaybe
port'' :: Integer
port'' = case (port', scheme) of
("", "http") -> 80
("", "https") -> 443
_ -> read $ unpack $ tail $ pack port'
_ -> readPort $ unpack $ tail $ pack port'
isUriValid:: URI -> Bool
isUriValid = fAnd [isSchemeValid, isQueryValid, isAuthorityValid]
@@ -325,6 +325,7 @@ isHostValid _ = True
isPortValid :: URIAuth -> Bool
isPortValid URIAuth {uriPort = ""} = True
isPortValid URIAuth {uriPort = (':':p)} =
let i :: Integer = read p in
i > 0 && i < 65536
case readMaybe p of
Just i -> i > (0 :: Integer) && i < 65536
Nothing -> False
isPortValid _ = False
+8 -10
View File
@@ -17,13 +17,11 @@ import qualified Data.ByteString.Char8 as BS
import Data.Ranged.Boundaries
import Data.Ranged.Ranges
import Data.String.Conversions (cs)
import Text.Read (readMaybe)
import Text.Regex.TDFA ((=~))
import Data.Maybe (fromMaybe, listToMaybe)
import Data.List (lookup)
import Prelude
import Protolude
type NonnegRange = Range Integer
@@ -33,9 +31,9 @@ rangeParse range = do
case listToMaybe (range =~ rangeRegex :: [[BS.ByteString]]) of
Just parsedRange ->
let [_, from, to] = readMaybe . cs <$> parsedRange
lower = fromMaybe emptyRange (rangeGeq <$> from)
upper = fromMaybe allRange (rangeLeq <$> to) in
let [_, mLower, mUpper] = readMaybe . toS <$> parsedRange
lower = fromMaybe emptyRange (rangeGeq <$> mLower)
upper = fromMaybe allRange (rangeLeq <$> mUpper) in
rangeIntersection lower upper
Nothing -> allRange
@@ -52,14 +50,14 @@ restrictRange (Just limit) r =
rangeLimit :: NonnegRange -> Maybe Integer
rangeLimit range =
case [rangeLower range, rangeUpper range] of
[BoundaryBelow from, BoundaryAbove to] -> Just (1 + to - from)
[BoundaryBelow lower, BoundaryAbove upper] -> Just (1 + upper - lower)
_ -> Nothing
rangeOffset :: NonnegRange -> Integer
rangeOffset range =
case rangeLower range of
BoundaryBelow from -> from
_ -> error "range without lower bound" -- should never happen
BoundaryBelow lower -> lower
_ -> panic "range without lower bound" -- should never happen
rangeGeq :: Integer -> NonnegRange
rangeGeq n =