refactor: rename ContentType to MediaType

The core type was wrongly named as the header
This commit is contained in:
steve-chavez
2022-07-06 13:47:59 -05:00
committed by Steve Chavez
parent 3e83bef9c4
commit 7a7ceaf39a
9 changed files with 187 additions and 187 deletions
+1 -1
View File
@@ -44,7 +44,6 @@ library
PostgREST.Config.JSPath PostgREST.Config.JSPath
PostgREST.Config.PgVersion PostgREST.Config.PgVersion
PostgREST.Config.Proxy PostgREST.Config.Proxy
PostgREST.ContentType
PostgREST.Cors PostgREST.Cors
PostgREST.DbStructure PostgREST.DbStructure
PostgREST.DbStructure.Identifiers PostgREST.DbStructure.Identifiers
@@ -55,6 +54,7 @@ library
PostgREST.GucHeader PostgREST.GucHeader
PostgREST.Logger PostgREST.Logger
PostgREST.Middleware PostgREST.Middleware
PostgREST.MediaType
PostgREST.OpenAPI PostgREST.OpenAPI
PostgREST.Query.QueryBuilder PostgREST.Query.QueryBuilder
PostgREST.Query.SqlFragment PostgREST.Query.SqlFragment
+31 -31
View File
@@ -61,7 +61,6 @@ import PostgREST.Config (AppConfig (..),
LogLevel (..), LogLevel (..),
OpenAPIMode (..)) OpenAPIMode (..))
import PostgREST.Config.PgVersion (PgVersion (..)) import PostgREST.Config.PgVersion (PgVersion (..))
import PostgREST.ContentType (ContentType (..))
import PostgREST.DbStructure (DbStructure (..)) import PostgREST.DbStructure (DbStructure (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
@@ -73,6 +72,7 @@ import PostgREST.Error (Error)
import PostgREST.GucHeader (GucHeader, import PostgREST.GucHeader (GucHeader,
addHeadersIfNotIncluded, addHeadersIfNotIncluded,
unwrapGucHeader) unwrapGucHeader)
import PostgREST.MediaType (MediaType (..))
import PostgREST.Request.ApiRequest (Action (..), import PostgREST.Request.ApiRequest (Action (..),
ApiRequest (..), ApiRequest (..),
InvokeMethod (..), InvokeMethod (..),
@@ -86,8 +86,8 @@ import PostgREST.Request.ReadQuery (ReadRequest, fstFieldNames)
import PostgREST.Version (prettyVersion) import PostgREST.Version (prettyVersion)
import PostgREST.Workers (connectionWorker, listener) import PostgREST.Workers (connectionWorker, listener)
import qualified PostgREST.ContentType as ContentType
import qualified PostgREST.DbStructure.Proc as Proc import qualified PostgREST.DbStructure.Proc as Proc
import qualified PostgREST.MediaType as MediaType
import Protolude hiding (Handler) import Protolude hiding (Handler)
@@ -267,11 +267,11 @@ handleRead headersOnly identifier context@RequestContext{..} = do
else else
countQuery countQuery
) )
(iAcceptContentType == CTSingularJSON) (iAcceptMediaType == MTSingularJSON)
(shouldCount iPreferCount) (shouldCount iPreferCount)
(iAcceptContentType == CTTextCSV) (iAcceptMediaType == MTTextCSV)
(iAcceptContentType == CTTextXML) (iAcceptMediaType == MTTextXML)
(iAcceptContentType == CTGeoJSON) (iAcceptMediaType == MTGeoJSON)
bField bField
configDbPreparedStatements configDbPreparedStatements
@@ -290,7 +290,7 @@ handleRead headersOnly identifier context@RequestContext{..} = do
] ]
++ contentTypeHeaders context ++ contentTypeHeaders context
failNotSingular iAcceptContentType queryTotal . response status headers $ failNotSingular iAcceptMediaType queryTotal . response status headers $
if headersOnly then mempty else LBS.fromStrict body if headersOnly then mempty else LBS.fromStrict body
readTotal :: AppConfig -> ApiRequest -> Maybe Int64 -> SQL.Snippet -> DbHandler (Maybe Int64) readTotal :: AppConfig -> ApiRequest -> Maybe Int64 -> SQL.Snippet -> DbHandler (Maybe Int64)
@@ -341,7 +341,7 @@ handleCreate identifier@QualifiedIdentifier{..} context@RequestContext{..} = do
toAppliedHeader <$> iPreferResolution toAppliedHeader <$> iPreferResolution
] ]
failNotSingular iAcceptContentType resQueryTotal $ failNotSingular iAcceptMediaType resQueryTotal $
if iPreferRepresentation == Full then if iPreferRepresentation == Full then
response HTTP.status201 (headers ++ contentTypeHeaders context) (LBS.fromStrict resBody) response HTTP.status201 (headers ++ contentTypeHeaders context) (LBS.fromStrict resBody)
else else
@@ -368,7 +368,7 @@ handleUpdate identifier context@RequestContext{..} = do
if shouldCount iPreferCount then Just resQueryTotal else Nothing if shouldCount iPreferCount then Just resQueryTotal else Nothing
failChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resQueryTotal =<< failChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resQueryTotal =<<
failNotSingular iAcceptContentType resQueryTotal ( failNotSingular iAcceptMediaType resQueryTotal (
if fullRepr then if fullRepr then
response status (contentTypeHeaders context ++ [contentRangeHeader]) (LBS.fromStrict resBody) response status (contentTypeHeaders context ++ [contentRangeHeader]) (LBS.fromStrict resBody)
else else
@@ -408,7 +408,7 @@ handleDelete identifier context@(RequestContext _ _ ApiRequest{..} _) = do
if shouldCount iPreferCount then Just resQueryTotal else Nothing if shouldCount iPreferCount then Just resQueryTotal else Nothing
failChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resQueryTotal =<< failChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resQueryTotal =<<
failNotSingular iAcceptContentType resQueryTotal ( failNotSingular iAcceptMediaType resQueryTotal (
if iPreferRepresentation == Full then if iPreferRepresentation == Full then
response HTTP.status200 response HTTP.status200
(contentTypeHeaders context ++ [contentRangeHeader]) (contentTypeHeaders context ++ [contentRangeHeader])
@@ -462,10 +462,10 @@ handleInvoke invMethod proc context@RequestContext{..} = do
(QueryBuilder.readRequestToQuery req) (QueryBuilder.readRequestToQuery req)
(QueryBuilder.readRequestToCountQuery req) (QueryBuilder.readRequestToCountQuery req)
(shouldCount iPreferCount) (shouldCount iPreferCount)
(iAcceptContentType == CTSingularJSON) (iAcceptMediaType == MTSingularJSON)
(iAcceptContentType == CTTextCSV) (iAcceptMediaType == MTTextCSV)
(iAcceptContentType == CTTextXML) (iAcceptMediaType == MTTextXML)
(iAcceptContentType == CTGeoJSON) (iAcceptMediaType == MTGeoJSON)
(iPreferParameters == Just MultipleObjects) (iPreferParameters == Just MultipleObjects)
bField bField
(configDbPreparedStatements ctxConfig) (configDbPreparedStatements ctxConfig)
@@ -476,7 +476,7 @@ handleInvoke invMethod proc context@RequestContext{..} = do
(status, contentRange) = (status, contentRange) =
RangeQuery.rangeStatusHeader iTopLevelRange queryTotal tableTotal RangeQuery.rangeStatusHeader iTopLevelRange queryTotal tableTotal
failNotSingular iAcceptContentType queryTotal $ failNotSingular iAcceptMediaType queryTotal $
if Proc.procReturnsVoid proc then if Proc.procReturnsVoid proc then
response HTTP.status204 [contentRange] mempty response HTTP.status204 [contentRange] mempty
else else
@@ -503,7 +503,7 @@ handleOpenApi headersOnly tSchema (RequestContext conf@AppConfig{..} dbStructure
return $ return $
Wai.responseLBS HTTP.status200 Wai.responseLBS HTTP.status200
(ContentType.toHeader CTOpenAPI : maybeToList (profileHeader apiRequest)) (MediaType.toContentType MTOpenAPI : maybeToList (profileHeader apiRequest))
(if headersOnly then mempty else body) (if headersOnly then mempty else body)
txMode :: ApiRequest -> SQL.Mode txMode :: ApiRequest -> SQL.Mode
@@ -550,10 +550,10 @@ writeQuery mutation identifier@QualifiedIdentifier{..} isInsert pkCols context@R
Statements.createWriteStatement Statements.createWriteStatement
(QueryBuilder.readRequestToQuery readReq) (QueryBuilder.readRequestToQuery readReq)
(QueryBuilder.mutateRequestToQuery mutateReq) (QueryBuilder.mutateRequestToQuery mutateReq)
(iAcceptContentType ctxApiRequest == CTSingularJSON) (iAcceptMediaType ctxApiRequest == MTSingularJSON)
isInsert isInsert
(iAcceptContentType ctxApiRequest == CTTextCSV) (iAcceptMediaType ctxApiRequest == MTTextCSV)
(iAcceptContentType ctxApiRequest == CTGeoJSON) (iAcceptMediaType ctxApiRequest == MTGeoJSON)
(iPreferRepresentation ctxApiRequest) (iPreferRepresentation ctxApiRequest)
pkCols pkCols
(configDbPreparedStatements ctxConfig) (configDbPreparedStatements ctxConfig)
@@ -575,9 +575,9 @@ gucResponse gucStatus gucHeaders status headers =
-- | -- |
-- Fail a response if a single JSON object was requested and not exactly one -- Fail a response if a single JSON object was requested and not exactly one
-- was found. -- was found.
failNotSingular :: ContentType -> Int64 -> Wai.Response -> DbHandler Wai.Response failNotSingular :: MediaType -> Int64 -> Wai.Response -> DbHandler Wai.Response
failNotSingular contentType queryTotal response = failNotSingular mediaType queryTotal response =
if contentType == CTSingularJSON && queryTotal /= 1 then if mediaType == MTSingularJSON && queryTotal /= 1 then
do do
lift SQL.condemn lift SQL.condemn
throwError $ Error.singularityError queryTotal throwError $ Error.singularityError queryTotal
@@ -611,16 +611,16 @@ readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure ap
contentTypeHeaders :: RequestContext -> [HTTP.Header] contentTypeHeaders :: RequestContext -> [HTTP.Header]
contentTypeHeaders RequestContext{..} = contentTypeHeaders RequestContext{..} =
ContentType.toHeader (iAcceptContentType ctxApiRequest) : maybeToList (profileHeader ctxApiRequest) MediaType.toContentType (iAcceptMediaType ctxApiRequest) : maybeToList (profileHeader ctxApiRequest)
-- | If raw(binary) output is requested, check that ContentType is one of the -- | If raw(binary) output is requested, check that MediaType is one of the
-- admitted rawContentTypes and that`?select=...` contains only one field other -- admitted rawMediaTypes and that`?select=...` contains only one field other
-- than `*` -- than `*`
binaryField :: Monad m => RequestContext -> ReadRequest -> Handler m (Maybe FieldName) binaryField :: Monad m => RequestContext -> ReadRequest -> Handler m (Maybe FieldName)
binaryField RequestContext{..} readReq binaryField RequestContext{..} readReq
| returnsScalar (iTarget ctxApiRequest) && iAcceptContentType ctxApiRequest `elem` rawContentTypes ctxConfig = | returnsScalar (iTarget ctxApiRequest) && iAcceptMediaType ctxApiRequest `elem` rawMediaTypes ctxConfig =
return $ Just "pgrst_scalar" return $ Just "pgrst_scalar"
| iAcceptContentType ctxApiRequest `elem` rawContentTypes ctxConfig = | iAcceptMediaType ctxApiRequest `elem` rawMediaTypes ctxConfig =
let let
fldNames = fstFieldNames readReq fldNames = fstFieldNames readReq
fieldName = headMay fldNames fieldName = headMay fldNames
@@ -628,13 +628,13 @@ binaryField RequestContext{..} readReq
if length fldNames == 1 && fieldName /= Just "*" then if length fldNames == 1 && fieldName /= Just "*" then
return fieldName return fieldName
else else
throwError $ Error.BinaryFieldError (iAcceptContentType ctxApiRequest) throwError $ Error.BinaryFieldError (iAcceptMediaType ctxApiRequest)
| otherwise = | otherwise =
return Nothing return Nothing
rawContentTypes :: AppConfig -> [ContentType] rawMediaTypes :: AppConfig -> [MediaType]
rawContentTypes AppConfig{..} = rawMediaTypes AppConfig{..} =
(ContentType.decodeContentType <$> configRawMediaTypes) `union` [CTOctetStream, CTTextPlain, CTTextXML] (MediaType.decodeMediaType <$> configRawMediaTypes) `union` [MTOctetStream, MTTextPlain, MTTextXML]
profileHeader :: ApiRequest -> Maybe HTTP.Header profileHeader :: ApiRequest -> Maybe HTTP.Header
profileHeader ApiRequest{..} = profileHeader ApiRequest{..} =
-70
View File
@@ -1,70 +0,0 @@
{-# LANGUAGE DuplicateRecordFields #-}
module PostgREST.ContentType
( ContentType(..)
, toHeader
, toMime
, decodeContentType
) where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS (c2w)
import Network.HTTP.Types.Header (Header, hContentType)
import Protolude
-- | Enumeration of currently supported response content types
data ContentType
= CTApplicationJSON
| CTSingularJSON
| CTGeoJSON
| CTTextCSV
| CTTextPlain
| CTTextXML
| CTOpenAPI
| CTUrlEncoded
| CTOctetStream
| CTAny
| CTOther ByteString
deriving (Eq)
-- | Convert from ContentType to a full HTTP Header
toHeader :: ContentType -> Header
toHeader ct = (hContentType, toMime ct <> charset)
where
charset = case ct of
CTOctetStream -> mempty
CTOther _ -> mempty
_ -> "; charset=utf-8"
-- | Convert from ContentType to a ByteString representing the mime type
toMime :: ContentType -> ByteString
toMime CTApplicationJSON = "application/json"
toMime CTGeoJSON = "application/geo+json"
toMime CTTextCSV = "text/csv"
toMime CTTextPlain = "text/plain"
toMime CTTextXML = "text/xml"
toMime CTOpenAPI = "application/openapi+json"
toMime CTSingularJSON = "application/vnd.pgrst.object+json"
toMime CTUrlEncoded = "application/x-www-form-urlencoded"
toMime CTOctetStream = "application/octet-stream"
toMime CTAny = "*/*"
toMime (CTOther ct) = ct
-- | Convert from ByteString to ContentType. Warning: discards MIME parameters
decodeContentType :: BS.ByteString -> ContentType
decodeContentType ct =
case BS.takeWhile (/= BS.c2w ';') ct of
"application/json" -> CTApplicationJSON
"application/geo+json" -> CTGeoJSON
"text/csv" -> CTTextCSV
"text/plain" -> CTTextPlain
"text/xml" -> CTTextXML
"application/openapi+json" -> CTOpenAPI
"application/vnd.pgrst.object+json" -> CTSingularJSON
"application/vnd.pgrst.object" -> CTSingularJSON
"application/x-www-form-urlencoded" -> CTUrlEncoded
"application/octet-stream" -> CTOctetStream
"*/*" -> CTAny
ct' -> CTOther ct'
+19 -19
View File
@@ -29,8 +29,8 @@ import Network.Wai (Response, responseLBS)
import Network.HTTP.Types.Header (Header) import Network.HTTP.Types.Header (Header)
import PostgREST.ContentType (ContentType (..)) import PostgREST.MediaType (MediaType (..))
import qualified PostgREST.ContentType as ContentType import qualified PostgREST.MediaType as MediaType
import PostgREST.Request.Types (ApiRequestError (..), import PostgREST.Request.Types (ApiRequestError (..),
QPError (..)) QPError (..))
@@ -57,7 +57,7 @@ instance PgrstError ApiRequestError where
status ActionInappropriate = HTTP.status405 status ActionInappropriate = HTTP.status405
status AmbiguousRelBetween{} = HTTP.status300 status AmbiguousRelBetween{} = HTTP.status300
status AmbiguousRpc{} = HTTP.status300 status AmbiguousRpc{} = HTTP.status300
status ContentTypeError{} = HTTP.status415 status MediaTypeError{} = HTTP.status415
status InvalidBody{} = HTTP.status400 status InvalidBody{} = HTTP.status400
status InvalidFilters = HTTP.status405 status InvalidFilters = HTTP.status405
status InvalidRange = HTTP.status416 status InvalidRange = HTTP.status416
@@ -70,7 +70,7 @@ instance PgrstError ApiRequestError where
status UnacceptableSchema{} = HTTP.status406 status UnacceptableSchema{} = HTTP.status406
status LimitNoOrderError = HTTP.status400 status LimitNoOrderError = HTTP.status400
headers _ = [ContentType.toHeader CTApplicationJSON] headers _ = [MediaType.toContentType MTApplicationJSON]
instance JSON.ToJSON ApiRequestError where instance JSON.ToJSON ApiRequestError where
toJSON (QueryParamError (QPError message details)) = JSON.object [ toJSON (QueryParamError (QPError message details)) = JSON.object [
@@ -108,9 +108,9 @@ instance JSON.ToJSON ApiRequestError where
"message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas), "message" .= ("The schema must be one of the following: " <> T.intercalate ", " schemas),
"details" .= JSON.Null, "details" .= JSON.Null,
"hint" .= JSON.Null] "hint" .= JSON.Null]
toJSON (ContentTypeError cts) = JSON.object [ toJSON (MediaTypeError cts) = JSON.object [
"code" .= ApiRequestErrorCode07, "code" .= ApiRequestErrorCode07,
"message" .= ("None of these Content-Types are available: " <> T.intercalate ", " (map T.decodeUtf8 cts)), "message" .= ("None of these media types are available: " <> T.intercalate ", " (map T.decodeUtf8 cts)),
"details" .= JSON.Null, "details" .= JSON.Null,
"hint" .= JSON.Null] "hint" .= JSON.Null]
toJSON (NotEmbedded resource) = JSON.object [ toJSON (NotEmbedded resource) = JSON.object [
@@ -147,10 +147,10 @@ instance JSON.ToJSON ApiRequestError where
"message" .= ("Could not find the " <> schema <> "." <> procName <> "message" .= ("Could not find the " <> schema <> "." <> procName <>
(case (hasPreferSingleObject, isInvPost, contentType) of (case (hasPreferSingleObject, isInvPost, contentType) of
(True, _, _) -> " function with a single json or jsonb parameter" (True, _, _) -> " function with a single json or jsonb parameter"
(_, True, CTTextPlain) -> " function with a single unnamed text parameter" (_, True, MTTextPlain) -> " function with a single unnamed text parameter"
(_, True, CTTextXML) -> " function with a single unnamed xml parameter" (_, True, MTTextXML) -> " function with a single unnamed xml parameter"
(_, True, CTOctetStream) -> " function with a single unnamed bytea parameter" (_, True, MTOctetStream) -> " function with a single unnamed bytea parameter"
(_, True, CTApplicationJSON) -> prms <> " function or the " <> schema <> "." <> procName <>" function with a single unnamed json or jsonb parameter" (_, True, MTApplicationJSON) -> prms <> " function or the " <> schema <> "." <> procName <>" function with a single unnamed json or jsonb parameter"
_ -> prms <> " function") <> _ -> prms <> " function") <>
" in the schema cache"), " in the schema cache"),
"details" .= JSON.Null, "details" .= JSON.Null,
@@ -200,8 +200,8 @@ instance PgrstError PgError where
headers err = headers err =
if status err == HTTP.status401 if status err == HTTP.status401
then [ContentType.toHeader CTApplicationJSON, ("WWW-Authenticate", "Bearer") :: Header] then [MediaType.toContentType MTApplicationJSON, ("WWW-Authenticate", "Bearer") :: Header]
else [ContentType.toHeader CTApplicationJSON] else [MediaType.toContentType MTApplicationJSON]
instance JSON.ToJSON PgError where instance JSON.ToJSON PgError where
toJSON (PgError _ usageError) = JSON.toJSON usageError toJSON (PgError _ usageError) = JSON.toJSON usageError
@@ -304,7 +304,7 @@ checkIsFatal _ = Nothing
data Error data Error
= ApiRequestError ApiRequestError = ApiRequestError ApiRequestError
| BinaryFieldError ContentType | BinaryFieldError MediaType
| GucHeadersError | GucHeadersError
| GucStatusError | GucStatusError
| JwtTokenInvalid Text | JwtTokenInvalid Text
@@ -335,11 +335,11 @@ instance PgrstError Error where
status UnsupportedVerb{} = HTTP.status405 status UnsupportedVerb{} = HTTP.status405
headers (ApiRequestError err) = headers err headers (ApiRequestError err) = headers err
headers (JwtTokenInvalid m) = [ContentType.toHeader CTApplicationJSON, invalidTokenHeader m] headers (JwtTokenInvalid m) = [MediaType.toContentType MTApplicationJSON, invalidTokenHeader m]
headers JwtTokenRequired = [ContentType.toHeader CTApplicationJSON, requiredTokenHeader] headers JwtTokenRequired = [MediaType.toContentType MTApplicationJSON, requiredTokenHeader]
headers (PgErr err) = headers err headers (PgErr err) = headers err
headers SingularityError{} = [ContentType.toHeader CTSingularJSON] headers SingularityError{} = [MediaType.toContentType MTSingularJSON]
headers _ = [ContentType.toHeader CTApplicationJSON] headers _ = [MediaType.toContentType MTApplicationJSON]
instance JSON.ToJSON Error where instance JSON.ToJSON Error where
toJSON NoSchemaCacheError = JSON.object [ toJSON NoSchemaCacheError = JSON.object [
@@ -382,7 +382,7 @@ instance JSON.ToJSON Error where
"hint" .= JSON.Null] "hint" .= JSON.Null]
toJSON (BinaryFieldError ct) = JSON.object [ toJSON (BinaryFieldError ct) = JSON.object [
"code" .= ApiRequestErrorCode13, "code" .= ApiRequestErrorCode13,
"message" .= ((T.decodeUtf8 (ContentType.toMime ct) <> " requested but more than one column was selected") :: Text), "message" .= ((T.decodeUtf8 (MediaType.toMime ct) <> " requested but more than one column was selected") :: Text),
"details" .= JSON.Null, "details" .= JSON.Null,
"hint" .= JSON.Null] "hint" .= JSON.Null]
@@ -395,7 +395,7 @@ instance JSON.ToJSON Error where
toJSON (SingularityError n) = JSON.object [ toJSON (SingularityError n) = JSON.object [
"code" .= ApiRequestErrorCode16, "code" .= ApiRequestErrorCode16,
"message" .= ("JSON object requested, multiple (or no) rows returned" :: Text), "message" .= ("JSON object requested, multiple (or no) rows returned" :: Text),
"details" .= T.unwords ["Results contain", show n, "rows,", T.decodeUtf8 (ContentType.toMime CTSingularJSON), "requires 1 row"], "details" .= T.unwords ["Results contain", show n, "rows,", T.decodeUtf8 (MediaType.toMime MTSingularJSON), "requires 1 row"],
"hint" .= JSON.Null] "hint" .= JSON.Null]
toJSON (UnsupportedVerb verb) = JSON.object [ toJSON (UnsupportedVerb verb) = JSON.object [
+70
View File
@@ -0,0 +1,70 @@
{-# LANGUAGE DuplicateRecordFields #-}
module PostgREST.MediaType
( MediaType(..)
, toContentType
, toMime
, decodeMediaType
) where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS (c2w)
import Network.HTTP.Types.Header (Header, hContentType)
import Protolude
-- | Enumeration of currently supported media types
data MediaType
= MTApplicationJSON
| MTSingularJSON
| MTGeoJSON
| MTTextCSV
| MTTextPlain
| MTTextXML
| MTOpenAPI
| MTUrlEncoded
| MTOctetStream
| MTAny
| MTOther ByteString
deriving (Eq)
-- | Convert MediaType to a Content-Type HTTP Header
toContentType :: MediaType -> Header
toContentType ct = (hContentType, toMime ct <> charset)
where
charset = case ct of
MTOctetStream -> mempty
MTOther _ -> mempty
_ -> "; charset=utf-8"
-- | Convert from MediaType to a ByteString representing the mime type
toMime :: MediaType -> ByteString
toMime MTApplicationJSON = "application/json"
toMime MTGeoJSON = "application/geo+json"
toMime MTTextCSV = "text/csv"
toMime MTTextPlain = "text/plain"
toMime MTTextXML = "text/xml"
toMime MTOpenAPI = "application/openapi+json"
toMime MTSingularJSON = "application/vnd.pgrst.object+json"
toMime MTUrlEncoded = "application/x-www-form-urlencoded"
toMime MTOctetStream = "application/octet-stream"
toMime MTAny = "*/*"
toMime (MTOther ct) = ct
-- | Convert from ByteString to MediaType. Warning: discards MIME parameters
decodeMediaType :: BS.ByteString -> MediaType
decodeMediaType ct =
case BS.takeWhile (/= BS.c2w ';') ct of
"application/json" -> MTApplicationJSON
"application/geo+json" -> MTGeoJSON
"text/csv" -> MTTextCSV
"text/plain" -> MTTextPlain
"text/xml" -> MTTextXML
"application/openapi+json" -> MTOpenAPI
"application/vnd.pgrst.object+json" -> MTSingularJSON
"application/vnd.pgrst.object" -> MTSingularJSON
"application/x-www-form-urlencoded" -> MTUrlEncoded
"application/octet-stream" -> MTOctetStream
"*/*" -> MTAny
ct' -> MTOther ct'
+6 -6
View File
@@ -37,7 +37,7 @@ import PostgREST.DbStructure.Table (Column (..), Table (..),
TablesMap) TablesMap)
import PostgREST.Version (docsVersion, prettyVersion) import PostgREST.Version (docsVersion, prettyVersion)
import PostgREST.ContentType import PostgREST.MediaType
import Protolude hiding (Proxy, get) import Protolude hiding (Proxy, get)
@@ -51,7 +51,7 @@ encode conf dbStructure tables procs schemaDescription =
(proxyUri conf) (proxyUri conf)
schemaDescription schemaDescription
makeMimeList :: [ContentType] -> MimeList makeMimeList :: [MediaType] -> MimeList
makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs
toSwaggerType :: Text -> Maybe (SwaggerType t) toSwaggerType :: Text -> Maybe (SwaggerType t)
@@ -294,7 +294,7 @@ makeProcPathItem pd = ("/rpc/" ++ toS (pdName pd), pe)
& description .~ mfilter (/="") pDesc & description .~ mfilter (/="") pDesc
& parameters .~ makeProcParam pd & parameters .~ makeProcParam pd
& tags .~ Set.fromList ["(rpc) " <> pdName pd] & tags .~ Set.fromList ["(rpc) " <> pdName pd]
& produces ?~ makeMimeList [CTApplicationJSON, CTSingularJSON] & produces ?~ makeMimeList [MTApplicationJSON, MTSingularJSON]
& at 200 ?~ "OK" & at 200 ?~ "OK"
pe = (mempty :: PathItem) & post ?~ postOp pe = (mempty :: PathItem) & post ?~ postOp
@@ -304,7 +304,7 @@ makeRootPathItem = ("/", p)
getOp = (mempty :: Operation) getOp = (mempty :: Operation)
& tags .~ Set.fromList ["Introspection"] & tags .~ Set.fromList ["Introspection"]
& summary ?~ "OpenAPI description (this document)" & summary ?~ "OpenAPI description (this document)"
& produces ?~ makeMimeList [CTOpenAPI, CTApplicationJSON] & produces ?~ makeMimeList [MTOpenAPI, MTApplicationJSON]
& at 200 ?~ "OK" & at 200 ?~ "OK"
pr = (mempty :: PathItem) & get ?~ getOp pr = (mempty :: PathItem) & get ?~ getOp
p = pr p = pr
@@ -336,8 +336,8 @@ postgrestSpec rels pds ti (s, h, p, b) sd = (mempty :: Swagger)
& definitions .~ fromList (makeTableDef rels <$> ti) & definitions .~ fromList (makeTableDef rels <$> ti)
& parameters .~ fromList (makeParamDefs ti) & parameters .~ fromList (makeParamDefs ti)
& paths .~ makePathItems pds ti & paths .~ makePathItems pds ti
& produces .~ makeMimeList [CTApplicationJSON, CTSingularJSON, CTTextCSV] & produces .~ makeMimeList [MTApplicationJSON, MTSingularJSON, MTTextCSV]
& consumes .~ makeMimeList [CTApplicationJSON, CTSingularJSON, CTTextCSV] & consumes .~ makeMimeList [MTApplicationJSON, MTSingularJSON, MTTextCSV]
where where
s' = if s == "http" then Http else Https s' = if s == "http" then Http else Https
h' = Just $ Host (T.unpack $ escapeHostName h) (Just (fromInteger p)) h' = Just $ Host (T.unpack $ escapeHostName h) (Just (fromInteger p))
+56 -56
View File
@@ -10,7 +10,7 @@ module PostgREST.Request.ApiRequest
( ApiRequest(..) ( ApiRequest(..)
, InvokeMethod(..) , InvokeMethod(..)
, Mutation(..) , Mutation(..)
, ContentType(..) , MediaType(..)
, Action(..) , Action(..)
, Target(..) , Target(..)
, Payload(..) , Payload(..)
@@ -45,13 +45,13 @@ import Web.Cookie (parseCookies)
import PostgREST.Config (AppConfig (..), import PostgREST.Config (AppConfig (..),
OpenAPIMode (..)) OpenAPIMode (..))
import PostgREST.ContentType (ContentType (..))
import PostgREST.DbStructure (DbStructure (..)) import PostgREST.DbStructure (DbStructure (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
Schema) Schema)
import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Proc (ProcDescription (..),
ProcParam (..), ProcsMap) ProcParam (..), ProcsMap)
import PostgREST.MediaType (MediaType (..))
import PostgREST.RangeQuery (NonnegRange, allRange, import PostgREST.RangeQuery (NonnegRange, allRange,
hasLimitZero, hasLimitZero,
limitZeroRange, limitZeroRange,
@@ -64,7 +64,7 @@ import PostgREST.Request.Preferences (PreferCount (..),
import PostgREST.Request.QueryParams (QueryParams (..)) import PostgREST.Request.QueryParams (QueryParams (..))
import PostgREST.Request.Types (ApiRequestError (..)) import PostgREST.Request.Types (ApiRequestError (..))
import qualified PostgREST.ContentType as ContentType import qualified PostgREST.MediaType as MediaType
import qualified PostgREST.Request.Preferences as Preferences import qualified PostgREST.Request.Preferences as Preferences
import qualified PostgREST.Request.QueryParams as QueryParams import qualified PostgREST.Request.QueryParams as QueryParams
@@ -173,7 +173,7 @@ data ApiRequest = ApiRequest {
, iMethod :: ByteString -- ^ Raw request method , iMethod :: ByteString -- ^ Raw request method
, iProfile :: Maybe Schema -- ^ The request profile for enabling use of multiple schemas. Follows the spec in hhttps://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/. , iProfile :: Maybe Schema -- ^ The request profile for enabling use of multiple schemas. Follows the spec in hhttps://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/.
, iSchema :: Schema -- ^ The request schema. Can vary depending on iProfile. , iSchema :: Schema -- ^ The request schema. Can vary depending on iProfile.
, iAcceptContentType :: ContentType , iAcceptMediaType :: MediaType
} }
-- | Examines HTTP request and translates it into user intent. -- | Examines HTTP request and translates it into user intent.
@@ -191,7 +191,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
| method `elem` ["PATCH", "DELETE"] && not (null qsRanges) && null qsOrder = Left LimitNoOrderError | method `elem` ["PATCH", "DELETE"] && not (null qsRanges) && null qsOrder = Left LimitNoOrderError
| method == "PUT" && topLevelRange /= allRange = Left PutRangeNotAllowedError | method == "PUT" && topLevelRange /= allRange = Left PutRangeNotAllowedError
| otherwise = do | otherwise = do
acceptContentType <- findAcceptContentType conf action path accepts acceptMediaType <- findAcceptMediaType conf action path accepts
checkedTarget <- target checkedTarget <- target
return ApiRequest { return ApiRequest {
iAction = action iAction = action
@@ -212,10 +212,10 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
, iMethod = method , iMethod = method
, iProfile = profile , iProfile = profile
, iSchema = schema , iSchema = schema
, iAcceptContentType = acceptContentType , iAcceptMediaType = acceptMediaType
} }
where where
accepts = maybe [CTAny] (map ContentType.decodeContentType . parseHttpAccept) $ lookupHeader "accept" accepts = maybe [MTAny] (map MediaType.decodeMediaType . parseHttpAccept) $ lookupHeader "accept"
expectParams = isTargetingProc && method /= "POST" expectParams = isTargetingProc && method /= "POST"
@@ -225,7 +225,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
isTargetingDefaultSpec = case path of isTargetingDefaultSpec = case path of
PathInfo{pIsDefaultSpec=True} -> True PathInfo{pIsDefaultSpec=True} -> True
_ -> False _ -> False
contentType = maybe CTApplicationJSON ContentType.decodeContentType $ lookupHeader "content-type" contentMediaType = maybe MTApplicationJSON MediaType.decodeMediaType $ lookupHeader "content-type"
columns = case action of columns = case action of
ActionMutate MutationCreate -> qsColumns ActionMutate MutationCreate -> qsColumns
@@ -234,33 +234,33 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
_ -> Nothing _ -> Nothing
payloadColumns = payloadColumns =
case (contentType, action) of case (contentMediaType, action) of
(_, ActionInvoke InvGet) -> S.fromList $ fst <$> qsParams (_, ActionInvoke InvGet) -> S.fromList $ fst <$> qsParams
(_, ActionInvoke InvHead) -> S.fromList $ fst <$> qsParams (_, ActionInvoke InvHead) -> S.fromList $ fst <$> qsParams
(CTUrlEncoded, _) -> S.fromList $ map (T.decodeUtf8 . fst) $ parseSimpleQuery $ LBS.toStrict reqBody (MTUrlEncoded, _) -> S.fromList $ map (T.decodeUtf8 . fst) $ parseSimpleQuery $ LBS.toStrict reqBody
_ -> case (relevantPayload, columns) of _ -> case (relevantPayload, columns) of
(Just ProcessedJSON{payKeys}, _) -> payKeys (Just ProcessedJSON{payKeys}, _) -> payKeys
(Just RawJSON{}, Just cls) -> cls (Just RawJSON{}, Just cls) -> cls
_ -> S.empty _ -> S.empty
payload :: Either ByteString Payload payload :: Either ByteString Payload
payload = case (contentType, isTargetingProc) of payload = case (contentMediaType, isTargetingProc) of
(CTApplicationJSON, _) -> (MTApplicationJSON, _) ->
if isJust columns if isJust columns
then Right $ RawJSON reqBody then Right $ RawJSON reqBody
else note "All object keys must match" . payloadAttributes reqBody else note "All object keys must match" . payloadAttributes reqBody
=<< if LBS.null reqBody && isTargetingProc =<< if LBS.null reqBody && isTargetingProc
then Right emptyObject then Right emptyObject
else first BS.pack $ JSON.eitherDecode reqBody else first BS.pack $ JSON.eitherDecode reqBody
(CTTextCSV, _) -> do (MTTextCSV, _) -> do
json <- csvToJson <$> first BS.pack (CSV.decodeByName reqBody) json <- csvToJson <$> first BS.pack (CSV.decodeByName reqBody)
note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json
(CTUrlEncoded, _) -> (MTUrlEncoded, _) ->
let paramsMap = HM.fromList $ (T.decodeUtf8 *** JSON.String . T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) in let paramsMap = HM.fromList $ (T.decodeUtf8 *** JSON.String . T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) in
Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap) Right $ ProcessedJSON (JSON.encode paramsMap) $ S.fromList (HM.keys paramsMap)
(CTTextPlain, True) -> Right $ RawPay reqBody (MTTextPlain, True) -> Right $ RawPay reqBody
(CTTextXML, True) -> Right $ RawPay reqBody (MTTextXML, True) -> Right $ RawPay reqBody
(CTOctetStream, True) -> Right $ RawPay reqBody (MTOctetStream, True) -> Right $ RawPay reqBody
(ct, _) -> Left $ "Content-Type not acceptable: " <> ContentType.toMime ct (ct, _) -> Left $ "Content-Type not acceptable: " <> MediaType.toMime ct
topLevelRange = fromMaybe allRange $ HM.lookup "limit" ranges -- if no limit is specified, get all the request rows topLevelRange = fromMaybe allRange $ HM.lookup "limit" ranges -- if no limit is specified, get all the request rows
action = action =
case method of case method of
@@ -300,7 +300,7 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
let let
callFindProc procSch procNam = findProc callFindProc procSch procNam = findProc
(QualifiedIdentifier procSch procNam) payloadColumns (preferParameters == Just SingleObject) (dbProcs dbStructure) (QualifiedIdentifier procSch procNam) payloadColumns (preferParameters == Just SingleObject) (dbProcs dbStructure)
contentType (action == ActionInvoke InvPost) contentMediaType (action == ActionInvoke InvPost)
in in
case path of case path of
PathInfo{pSchema, pName, pHasRpc, pIsRootSpec, pIsDefaultSpec} PathInfo{pSchema, pName, pHasRpc, pIsRootSpec, pIsDefaultSpec}
@@ -309,19 +309,19 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
| otherwise -> Right $ TargetIdent $ QualifiedIdentifier pSchema pName | otherwise -> Right $ TargetIdent $ QualifiedIdentifier pSchema pName
PathUnknown -> Right TargetUnknown PathUnknown -> Right TargetUnknown
shouldParsePayload = case (action, contentType) of shouldParsePayload = case (action, contentMediaType) of
(ActionMutate MutationCreate, _) -> True (ActionMutate MutationCreate, _) -> True
(ActionInvoke InvPost, CTUrlEncoded) -> False (ActionInvoke InvPost, MTUrlEncoded) -> False
(ActionInvoke InvPost, _) -> True (ActionInvoke InvPost, _) -> True
(ActionMutate MutationSingleUpsert, _) -> True (ActionMutate MutationSingleUpsert, _) -> True
(ActionMutate MutationUpdate, _) -> True (ActionMutate MutationUpdate, _) -> True
_ -> False _ -> False
relevantPayload = case (contentType, action) of relevantPayload = case (contentMediaType, action) of
-- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way -- Though ActionInvoke GET/HEAD doesn't really have a payload, we use the payload variable as a way
-- to store the query string arguments to the function. -- to store the query string arguments to the function.
(_, ActionInvoke InvGet) -> targetToJsonRpcParams (rightToMaybe target) qsParams (_, ActionInvoke InvGet) -> targetToJsonRpcParams (rightToMaybe target) qsParams
(_, ActionInvoke InvHead) -> targetToJsonRpcParams (rightToMaybe target) qsParams (_, ActionInvoke InvHead) -> targetToJsonRpcParams (rightToMaybe target) qsParams
(CTUrlEncoded, ActionInvoke InvPost) -> targetToJsonRpcParams (rightToMaybe target) $ (T.decodeUtf8 *** T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody) (MTUrlEncoded, ActionInvoke InvPost) -> targetToJsonRpcParams (rightToMaybe target) $ (T.decodeUtf8 *** T.decodeUtf8) <$> parseSimpleQuery (LBS.toStrict reqBody)
_ | shouldParsePayload -> rightToMaybe payload _ | shouldParsePayload -> rightToMaybe payload
| otherwise -> Nothing | otherwise -> Nothing
path = path =
@@ -348,15 +348,15 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange) isInvalidRange = topLevelRange == emptyRange && not (hasLimitZero limitRange)
{-| {-|
Find the best match from a list of content types accepted by the Find the best match from a list of media types accepted by the
client in order of decreasing preference and a list of types client in order of decreasing preference and a list of types
producible by the server. If there is no match but the client producible by the server. If there is no match but the client
accepts */* then return the top server pick. accepts */* then return the top server pick.
-} -}
mutuallyAgreeable :: [ContentType] -> [ContentType] -> Maybe ContentType mutuallyAgreeable :: [MediaType] -> [MediaType] -> Maybe MediaType
mutuallyAgreeable sProduces cAccepts = mutuallyAgreeable sProduces cAccepts =
let exact = listToMaybe $ L.intersect cAccepts sProduces in let exact = listToMaybe $ L.intersect cAccepts sProduces in
if isNothing exact && CTAny `elem` cAccepts if isNothing exact && MTAny `elem` cAccepts
then listToMaybe sProduces then listToMaybe sProduces
else exact else exact
@@ -409,42 +409,42 @@ payloadAttributes raw json =
where where
emptyPJArray = ProcessedJSON (JSON.encode emptyArray) S.empty emptyPJArray = ProcessedJSON (JSON.encode emptyArray) S.empty
findAcceptContentType :: AppConfig -> Action -> Path -> [ContentType] -> Either ApiRequestError ContentType findAcceptMediaType :: AppConfig -> Action -> Path -> [MediaType] -> Either ApiRequestError MediaType
findAcceptContentType conf action path accepts = findAcceptMediaType conf action path accepts =
case mutuallyAgreeable (requestContentTypes conf action path) accepts of case mutuallyAgreeable (requestMediaTypes conf action path) accepts of
Just ct -> Just ct ->
Right ct Right ct
Nothing -> Nothing ->
Left . ContentTypeError $ map ContentType.toMime accepts Left . MediaTypeError $ map MediaType.toMime accepts
requestContentTypes :: AppConfig -> Action -> Path -> [ContentType] requestMediaTypes :: AppConfig -> Action -> Path -> [MediaType]
requestContentTypes conf action path = requestMediaTypes conf action path =
case action of case action of
ActionRead _ -> defaultContentTypes ++ rawContentTypes conf ActionRead _ -> defaultMediaTypes ++ rawMediaTypes conf
ActionInvoke _ -> invokeContentTypes ActionInvoke _ -> invokeMediaTypes
ActionInspect _ -> [CTOpenAPI, CTApplicationJSON] ActionInspect _ -> [MTOpenAPI, MTApplicationJSON]
ActionInfo -> [CTTextCSV] ActionInfo -> [MTTextCSV]
_ -> defaultContentTypes _ -> defaultMediaTypes
where where
invokeContentTypes = invokeMediaTypes =
defaultContentTypes defaultMediaTypes
++ rawContentTypes conf ++ rawMediaTypes conf
++ [CTOpenAPI | pIsRootSpec path] ++ [MTOpenAPI | pIsRootSpec path]
defaultContentTypes = defaultMediaTypes =
[CTApplicationJSON, CTSingularJSON, CTGeoJSON, CTTextCSV] [MTApplicationJSON, MTSingularJSON, MTGeoJSON, MTTextCSV]
rawContentTypes :: AppConfig -> [ContentType] rawMediaTypes :: AppConfig -> [MediaType]
rawContentTypes AppConfig{..} = rawMediaTypes AppConfig{..} =
(ContentType.decodeContentType <$> configRawMediaTypes) `union` [CTOctetStream, CTTextPlain, CTTextXML] (MediaType.decodeMediaType <$> configRawMediaTypes) `union` [MTOctetStream, MTTextPlain, MTTextXML]
{-| {-|
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded, Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type. the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
-} -}
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> ContentType -> Bool -> Either ApiRequestError ProcDescription findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> MediaType -> Bool -> Either ApiRequestError ProcDescription
findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost = findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPost =
case matchProc of case matchProc of
([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) paramsAsSingleObject contentType isInvPost ([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) paramsAsSingleObject contentMediaType isInvPost
-- If there are no functions with named arguments, fallback to the single unnamed argument function -- If there are no functions with named arguments, fallback to the single unnamed argument function
([], [proc]) -> Right proc ([], [proc]) -> Right proc
([], procs) -> Left $ AmbiguousRpc (toList procs) ([], procs) -> Left $ AmbiguousRpc (toList procs)
@@ -462,12 +462,12 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost =
| otherwise = (ts,fs) | otherwise = (ts,fs)
-- If the function is called with post and has a single unnamed parameter -- If the function is called with post and has a single unnamed parameter
-- it can be called depending on content type and the parameter type -- it can be called depending on content type and the parameter type
hasSingleUnnamedParam ProcDescription{pdParams=[ProcParam{ppType}]} = isInvPost && case (contentType, ppType) of hasSingleUnnamedParam ProcDescription{pdParams=[ProcParam{ppType}]} = isInvPost && case (contentMediaType, ppType) of
(CTApplicationJSON, "json") -> True (MTApplicationJSON, "json") -> True
(CTApplicationJSON, "jsonb") -> True (MTApplicationJSON, "jsonb") -> True
(CTTextPlain, "text") -> True (MTTextPlain, "text") -> True
(CTTextXML, "xml") -> True (MTTextXML, "xml") -> True
(CTOctetStream, "bytea") -> True (MTOctetStream, "bytea") -> True
_ -> False _ -> False
hasSingleUnnamedParam _ = False hasSingleUnnamedParam _ = False
matchesParams proc = matchesParams proc =
@@ -480,7 +480,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentType isInvPost =
then length params == 1 && (firstType == Just "json" || firstType == Just "jsonb") then length params == 1 && (firstType == Just "json" || firstType == Just "jsonb")
-- If the function has no parameters, the arguments keys must be empty as well -- If the function has no parameters, the arguments keys must be empty as well
else if null params else if null params
then null argumentsKeys && not (isInvPost && contentType `elem` [CTOctetStream, CTTextPlain, CTTextXML]) then null argumentsKeys && not (isInvPost && contentMediaType `elem` [MTOctetStream, MTTextPlain, MTTextXML])
-- A function has optional and required parameters. Optional parameters have a default value and -- A function has optional and required parameters. Optional parameters have a default value and
-- don't require arguments for the function to be executed, required parameters must have an argument present. -- don't require arguments for the function to be executed, required parameters must have an argument present.
else case L.partition ppReq params of else case L.partition ppReq params of
+3 -3
View File
@@ -35,12 +35,12 @@ module PostgREST.Request.Types
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import PostgREST.ContentType (ContentType (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier) QualifiedIdentifier)
import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Proc (ProcDescription (..),
ProcParam (..)) ProcParam (..))
import PostgREST.DbStructure.Relationship (Relationship) import PostgREST.DbStructure.Relationship (Relationship)
import PostgREST.MediaType (MediaType (..))
import Protolude import Protolude
@@ -50,13 +50,13 @@ data ApiRequestError
= ActionInappropriate = ActionInappropriate
| AmbiguousRelBetween Text Text [Relationship] | AmbiguousRelBetween Text Text [Relationship]
| AmbiguousRpc [ProcDescription] | AmbiguousRpc [ProcDescription]
| ContentTypeError [ByteString] | MediaTypeError [ByteString]
| InvalidBody ByteString | InvalidBody ByteString
| InvalidFilters | InvalidFilters
| InvalidRange | InvalidRange
| LimitNoOrderError | LimitNoOrderError
| NoRelBetween Text Text Text | NoRelBetween Text Text Text
| NoRpc Text Text [Text] Bool ContentType Bool | NoRpc Text Text [Text] Bool MediaType Bool
| NotEmbedded Text | NotEmbedded Text
| ParseRequestError Text Text | ParseRequestError Text Text
| PutRangeNotAllowedError | PutRangeNotAllowedError
+1 -1
View File
@@ -909,7 +909,7 @@ spec actualPgVersion = do
request methodGet "/simple_pk" request methodGet "/simple_pk"
(acceptHdrs "text/unknowntype") "" (acceptHdrs "text/unknowntype") ""
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"None of these Content-Types are available: text/unknowntype","code":"PGRST107","details":null,"hint":null}|] [json|{"message":"None of these media types are available: text/unknowntype","code":"PGRST107","details":null,"hint":null}|]
{ matchStatus = 415 { matchStatus = 415
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }