Skip to content

Commit ee842eb

Browse files
committed
feat: sql handlers for custom media types
* test text/html and drop HtmlRawOutputSpec.hs * all tests passing, removed all pendingWith * make functions compatible with pg <= 12 * move custom media types tests to own spec * anyelement aggregate * apply aggregates without a final function * overriding works * overriding anyelement with particular agg * cannot override vendored media types * plan spec works with custom aggregate * renamed media types to make clear which ones are overridable * correct content negotiation with same weight * text/tab-separated-values media type * text/csv with BOM plus content-disposition header
1 parent d767398 commit ee842eb

28 files changed

+803
-384
lines changed

CHANGELOG.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
- #2943, Add `handling=strict/lenient` for Prefer header - @taimoorzaeem
1515
- #2983, Add more data to `Server-Timing` header - @develop7
1616
- #2441, Add config `server-cors-allowed-origins` to specify CORS origins - @taimoorzaeem
17+
- #2825, SQL handlers for custom media types - @steve-chavez
18+
- Solves #1548, #2699, #2763, #2170, #1462, #1102, #1374, #2901
1719

1820
### Fixed
1921

2022
- #3015, Fix unnecessary count() on RPC returning single - @steve-chavez
2123

24+
### Changed
25+
26+
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
27+
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
28+
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
29+
2230
## [11.2.2] - 2023-10-25
2331

2432
### Fixed
@@ -86,14 +94,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
8694
+ The `/table?select=*,other!fk(*)` must be used to disambiguate
8795
+ The server aids in choosing the `!fk` by sending a `hint` on the error whenever an ambiguous request happens.
8896

89-
### Changed
90-
91-
- #1462, #1548, Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
92-
+ Can be replaced with custom media types
93-
- #1462, #1548, Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
94-
+ Can be replaced with custom media types
95-
- #1462, #1548, Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
96-
9797
## [11.1.0] - 2023-06-07
9898

9999
### Added

postgrest.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,11 @@ test-suite spec
204204
Feature.OptionsSpec
205205
Feature.Query.AndOrParamsSpec
206206
Feature.Query.ComputedRelsSpec
207+
Feature.Query.CustomMediaSpec
207208
Feature.Query.DeleteSpec
208209
Feature.Query.EmbedDisambiguationSpec
209210
Feature.Query.EmbedInnerJoinSpec
210211
Feature.Query.ErrorSpec
211-
Feature.Query.HtmlRawOutputSpec
212212
Feature.Query.InsertSpec
213213
Feature.Query.JsonOperatorSpec
214214
Feature.Query.MultipleSchemaSpec

src/PostgREST/App.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A
220220
return $ pgrstResponse metrics pgrst
221221

222222
(ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do
223-
(planTime', iPlan) <- withTiming $ liftEither $ Plan.inspectPlan conf apiReq
223+
(planTime', iPlan) <- withTiming $ liftEither $ Plan.inspectPlan apiReq
224224
(rsTime', oaiResult) <- withTiming $ runQuery roleIsoLvl (Plan.ipTxmode iPlan) $ Query.openApiQuery sCache pgVer conf tSchema
225225
(renderTime', pgrst) <- withTiming $ liftEither $ Response.openApiResponse (T.decodeUtf8 prettyVersion, docsVersion) headersOnly oaiResult conf sCache iSchema iNegotiatedByProfile
226226
let metrics = Map.fromList [(SMPlan, planTime'), (SMQuery, rsTime'), (SMRender, renderTime'), jwtTime]

src/PostgREST/Error.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ instance PgrstError ApiRequestError where
9191
status SingularityError{} = HTTP.status406
9292
status PGRSTParseError = HTTP.status500
9393

94-
headers SingularityError{} = [MediaType.toContentType $ MTSingularJSON False]
94+
headers SingularityError{} = [MediaType.toContentType $ MTVndSingularJSON False]
9595
headers _ = mempty
9696

9797
toJsonPgrstError :: ErrorCode -> Text -> Maybe JSON.Value -> Maybe JSON.Value -> JSON.Value

src/PostgREST/MediaType.hs

+32-42
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
{-# LANGUAGE DeriveGeneric #-}
12
{-# LANGUAGE DuplicateRecordFields #-}
23

34
module PostgREST.MediaType
45
( MediaType(..)
5-
, MTPlanOption (..)
6-
, MTPlanFormat (..)
6+
, MTVndPlanOption (..)
7+
, MTVndPlanFormat (..)
78
, toContentType
89
, toMime
910
, decodeMediaType
@@ -19,8 +20,6 @@ import Protolude
1920
-- | Enumeration of currently supported media types
2021
data MediaType
2122
= MTApplicationJSON
22-
| MTArrayJSONStrip
23-
| MTSingularJSON Bool
2423
| MTGeoJSON
2524
| MTTextCSV
2625
| MTTextPlain
@@ -30,32 +29,23 @@ data MediaType
3029
| MTOctetStream
3130
| MTAny
3231
| MTOther ByteString
33-
-- TODO MTPlan should only have its options as [Text]. Its ResultAggregate should have the typed attributes.
34-
| MTPlan MediaType MTPlanFormat [MTPlanOption]
35-
deriving Show
36-
instance Eq MediaType where
37-
MTApplicationJSON == MTApplicationJSON = True
38-
MTArrayJSONStrip == MTArrayJSONStrip = True
39-
MTSingularJSON x == MTSingularJSON y = x == y
40-
MTGeoJSON == MTGeoJSON = True
41-
MTTextCSV == MTTextCSV = True
42-
MTTextPlain == MTTextPlain = True
43-
MTTextXML == MTTextXML = True
44-
MTOpenAPI == MTOpenAPI = True
45-
MTUrlEncoded == MTUrlEncoded = True
46-
MTOctetStream == MTOctetStream = True
47-
MTAny == MTAny = True
48-
MTOther x == MTOther y = x == y
49-
MTPlan{} == MTPlan{} = True
50-
_ == _ = False
32+
-- vendored media types
33+
| MTVndArrayJSONStrip
34+
| MTVndSingularJSON Bool
35+
-- TODO MTVndPlan should only have its options as [Text]. Its ResultAggregate should have the typed attributes.
36+
| MTVndPlan MediaType MTVndPlanFormat [MTVndPlanOption]
37+
deriving (Eq, Show, Generic)
38+
instance Hashable MediaType
5139

52-
data MTPlanOption
40+
data MTVndPlanOption
5341
= PlanAnalyze | PlanVerbose | PlanSettings | PlanBuffers | PlanWAL
54-
deriving (Eq, Show)
42+
deriving (Eq, Show, Generic)
43+
instance Hashable MTVndPlanOption
5544

56-
data MTPlanFormat
45+
data MTVndPlanFormat
5746
= PlanJSON | PlanText
58-
deriving (Eq, Show)
47+
deriving (Eq, Show, Generic)
48+
instance Hashable MTVndPlanFormat
5949

6050
-- | Convert MediaType to a Content-Type HTTP Header
6151
toContentType :: MediaType -> Header
@@ -69,31 +59,31 @@ toContentType ct = (hContentType, toMime ct <> charset)
6959
-- | Convert from MediaType to a ByteString representing the mime type
7060
toMime :: MediaType -> ByteString
7161
toMime MTApplicationJSON = "application/json"
72-
toMime MTArrayJSONStrip = "application/vnd.pgrst.array+json;nulls=stripped"
62+
toMime MTVndArrayJSONStrip = "application/vnd.pgrst.array+json;nulls=stripped"
7363
toMime MTGeoJSON = "application/geo+json"
7464
toMime MTTextCSV = "text/csv"
7565
toMime MTTextPlain = "text/plain"
7666
toMime MTTextXML = "text/xml"
7767
toMime MTOpenAPI = "application/openapi+json"
78-
toMime (MTSingularJSON True) = "application/vnd.pgrst.object+json;nulls=stripped"
79-
toMime (MTSingularJSON False) = "application/vnd.pgrst.object+json"
68+
toMime (MTVndSingularJSON True) = "application/vnd.pgrst.object+json;nulls=stripped"
69+
toMime (MTVndSingularJSON False) = "application/vnd.pgrst.object+json"
8070
toMime MTUrlEncoded = "application/x-www-form-urlencoded"
8171
toMime MTOctetStream = "application/octet-stream"
8272
toMime MTAny = "*/*"
8373
toMime (MTOther ct) = ct
84-
toMime (MTPlan mt fmt opts) =
74+
toMime (MTVndPlan mt fmt opts) =
8575
"application/vnd.pgrst.plan+" <> toMimePlanFormat fmt <>
8676
("; for=\"" <> toMime mt <> "\"") <>
8777
(if null opts then mempty else "; options=" <> BS.intercalate "|" (toMimePlanOption <$> opts))
8878

89-
toMimePlanOption :: MTPlanOption -> ByteString
79+
toMimePlanOption :: MTVndPlanOption -> ByteString
9080
toMimePlanOption PlanAnalyze = "analyze"
9181
toMimePlanOption PlanVerbose = "verbose"
9282
toMimePlanOption PlanSettings = "settings"
9383
toMimePlanOption PlanBuffers = "buffers"
9484
toMimePlanOption PlanWAL = "wal"
9585

96-
toMimePlanFormat :: MTPlanFormat -> ByteString
86+
toMimePlanFormat :: MTVndPlanFormat -> ByteString
9787
toMimePlanFormat PlanJSON = "json"
9888
toMimePlanFormat PlanText = "text"
9989

@@ -103,25 +93,25 @@ toMimePlanFormat PlanText = "text"
10393
-- MTApplicationJSON
10494
--
10595
-- >>> decodeMediaType "application/vnd.pgrst.plan;"
106-
-- MTPlan MTApplicationJSON PlanText []
96+
-- MTVndPlan MTApplicationJSON PlanText []
10797
--
10898
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
109-
-- MTPlan MTApplicationJSON PlanText []
99+
-- MTVndPlan MTApplicationJSON PlanText []
110100
--
111101
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
112-
-- MTPlan MTTextCSV PlanJSON []
102+
-- MTVndPlan MTTextCSV PlanJSON []
113103
--
114104
-- >>> decodeMediaType "application/vnd.pgrst.array+json;nulls=stripped"
115-
-- MTArrayJSONStrip
105+
-- MTVndArrayJSONStrip
116106
--
117107
-- >>> decodeMediaType "application/vnd.pgrst.array+json"
118108
-- MTApplicationJSON
119109
--
120110
-- >>> decodeMediaType "application/vnd.pgrst.object+json;nulls=stripped"
121-
-- MTSingularJSON True
111+
-- MTVndSingularJSON True
122112
--
123113
-- >>> decodeMediaType "application/vnd.pgrst.object+json"
124-
-- MTSingularJSON False
114+
-- MTVndSingularJSON False
125115

126116
decodeMediaType :: BS.ByteString -> MediaType
127117
decodeMediaType mt =
@@ -145,11 +135,11 @@ decodeMediaType mt =
145135
other:_ -> MTOther other
146136
_ -> MTAny
147137
where
148-
checkArrayNullStrip ["nulls=stripped"] = MTArrayJSONStrip
138+
checkArrayNullStrip ["nulls=stripped"] = MTVndArrayJSONStrip
149139
checkArrayNullStrip _ = MTApplicationJSON
150140

151-
checkSingularNullStrip ["nulls=stripped"] = MTSingularJSON True
152-
checkSingularNullStrip _ = MTSingularJSON False
141+
checkSingularNullStrip ["nulls=stripped"] = MTVndSingularJSON True
142+
checkSingularNullStrip _ = MTVndSingularJSON False
153143

154144
getPlan fmt rest =
155145
let
@@ -161,7 +151,7 @@ decodeMediaType mt =
161151
strippedFor <- BS.stripPrefix "for=" foundFor
162152
pure . decodeMediaType $ dropAround (== BS.c2w '"') strippedFor
163153
in
164-
MTPlan mtFor fmt $
154+
MTVndPlan mtFor fmt $
165155
[PlanAnalyze | inOpts "analyze" ] ++
166156
[PlanVerbose | inOpts "verbose" ] ++
167157
[PlanSettings | inOpts "settings"] ++

0 commit comments

Comments
 (0)