@@ -44,15 +44,15 @@ import PostgREST.ApiRequest (Action (..),
44
44
Payload (.. ))
45
45
import PostgREST.Config (AppConfig (.. ))
46
46
import PostgREST.Error (Error (.. ))
47
- import PostgREST.MediaType (MTPlanFormat (.. ),
48
- MediaType (.. ))
47
+ import PostgREST.MediaType (MediaType (.. ))
49
48
import PostgREST.Query.SqlFragment (sourceCTEName )
50
49
import PostgREST.RangeQuery (NonnegRange , allRange ,
51
50
convertToLimitZeroRange ,
52
51
restrictRange )
53
52
import PostgREST.SchemaCache (SchemaCache (.. ))
54
53
import PostgREST.SchemaCache.Identifiers (FieldName ,
55
54
QualifiedIdentifier (.. ),
55
+ RelIdentifier (.. ),
56
56
Schema )
57
57
import PostgREST.SchemaCache.Relationship (Cardinality (.. ),
58
58
Junction (.. ),
@@ -61,7 +61,7 @@ import PostgREST.SchemaCache.Relationship (Cardinality (..),
61
61
relIsToOne )
62
62
import PostgREST.SchemaCache.Representations (DataRepresentation (.. ),
63
63
RepresentationsMap )
64
- import PostgREST.SchemaCache.Routine (ResultAggregate (.. ),
64
+ import PostgREST.SchemaCache.Routine (MediaHandler (.. ),
65
65
Routine (.. ),
66
66
RoutineMap ,
67
67
RoutineParam (.. ),
@@ -93,25 +93,28 @@ import Protolude hiding (from)
93
93
data WrappedReadPlan = WrappedReadPlan {
94
94
wrReadPlan :: ReadPlanTree
95
95
, wrTxMode :: SQL. Mode
96
- , wrResAgg :: ResultAggregate
96
+ , wrHandler :: MediaHandler
97
97
, wrMedia :: MediaType
98
+ , wrIdent :: QualifiedIdentifier
98
99
}
99
100
100
101
data MutateReadPlan = MutateReadPlan {
101
102
mrReadPlan :: ReadPlanTree
102
103
, mrMutatePlan :: MutatePlan
103
104
, mrTxMode :: SQL. Mode
104
- , mrResAgg :: ResultAggregate
105
+ , mrHandler :: MediaHandler
105
106
, mrMedia :: MediaType
107
+ , mrIdent :: QualifiedIdentifier
106
108
}
107
109
108
110
data CallReadPlan = CallReadPlan {
109
111
crReadPlan :: ReadPlanTree
110
112
, crCallPlan :: CallPlan
111
113
, crTxMode :: SQL. Mode
112
114
, crProc :: Routine
113
- , crResAgg :: ResultAggregate
115
+ , crHandler :: MediaHandler
114
116
, crMedia :: MediaType
117
+ , crIdent :: QualifiedIdentifier
115
118
}
116
119
117
120
data InspectPlan = InspectPlan {
@@ -122,17 +125,17 @@ data InspectPlan = InspectPlan {
122
125
wrappedReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Either Error WrappedReadPlan
123
126
wrappedReadPlan identifier conf sCache apiRequest@ ApiRequest {iPreferences= Preferences {.. },.. } = do
124
127
rPlan <- readPlan identifier conf sCache apiRequest
125
- mediaType <- mapLeft ApiRequestError $ negotiateContent conf iAction iAcceptMediaType
128
+ (hdler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest identifier iAcceptMediaType (dbMediaHandlers sCache)
126
129
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
127
- return $ WrappedReadPlan rPlan SQL. Read (mediaToAggregate mediaType apiRequest) mediaType
130
+ return $ WrappedReadPlan rPlan SQL. Read hdler mediaType identifier
128
131
129
132
mutateReadPlan :: Mutation -> ApiRequest -> QualifiedIdentifier -> AppConfig -> SchemaCache -> Either Error MutateReadPlan
130
133
mutateReadPlan mutation apiRequest@ ApiRequest {iPreferences= Preferences {.. },.. } identifier conf sCache = do
131
134
rPlan <- readPlan identifier conf sCache apiRequest
132
135
mPlan <- mutatePlan mutation identifier apiRequest sCache rPlan
133
- mediaType <- mapLeft ApiRequestError $ negotiateContent conf iAction iAcceptMediaType
134
136
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
135
- return $ MutateReadPlan rPlan mPlan SQL. Write (mediaToAggregate mediaType apiRequest) mediaType
137
+ (hdler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest identifier iAcceptMediaType (dbMediaHandlers sCache)
138
+ return $ MutateReadPlan rPlan mPlan SQL. Write hdler mediaType identifier
136
139
137
140
callReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> InvokeMethod -> Either Error CallReadPlan
138
141
callReadPlan identifier conf sCache apiRequest@ ApiRequest {iPreferences= Preferences {.. },.. } invMethod = do
@@ -156,15 +159,19 @@ callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferenc
156
159
(InvPost , Routine. Immutable ) -> SQL. Read
157
160
(InvPost , Routine. Volatile ) -> SQL. Write
158
161
cPlan = callPlan proc apiRequest paramKeys args rPlan
159
- mediaType <- mapLeft ApiRequestError $ negotiateContent conf iAction iAcceptMediaType
162
+ (hdler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest relIdentifier iAcceptMediaType (dbMediaHandlers sCache)
160
163
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
161
- return $ CallReadPlan rPlan cPlan txMode proc (mediaToAggregate mediaType apiRequest) mediaType
164
+ return $ CallReadPlan rPlan cPlan txMode proc hdler mediaType relIdentifier
162
165
where
163
166
qsParams' = QueryParams. qsParams iQueryParams
164
167
165
- inspectPlan :: AppConfig -> ApiRequest -> Either Error InspectPlan
166
- inspectPlan conf apiRequest = do
167
- mediaType <- mapLeft ApiRequestError $ negotiateContent conf (iAction apiRequest) (iAcceptMediaType apiRequest)
168
+ inspectPlan :: ApiRequest -> Either Error InspectPlan
169
+ inspectPlan apiRequest = do
170
+ let producedMTs = [MTOpenAPI , MTApplicationJSON , MTAny ]
171
+ accepts = iAcceptMediaType apiRequest
172
+ mediaType <- if not . null $ L. intersect accepts producedMTs
173
+ then Right MTOpenAPI
174
+ else Left . ApiRequestError . MediaTypeError $ MediaType. toMime <$> accepts
168
175
return $ InspectPlan mediaType SQL. Read
169
176
170
177
{-|
@@ -824,52 +831,34 @@ inferColsEmbedNeeds (Node ReadPlan{select} forest) pkCols
824
831
addFilterToLogicForest :: CoercibleFilter -> [CoercibleLogicTree ] -> [CoercibleLogicTree ]
825
832
addFilterToLogicForest flt lf = CoercibleStmnt flt : lf
826
833
827
- mediaToAggregate :: MediaType -> ApiRequest -> ResultAggregate
828
- mediaToAggregate mt apiReq@ ApiRequest {iAction= act, iPreferences= Preferences {preferRepresentation= rep}} =
829
- if noAgg then NoAgg
830
- else case mt of
831
- MTApplicationJSON -> BuiltinAggJson
832
- MTSingularJSON strip -> BuiltinAggSingleJson strip
833
- MTArrayJSONStrip -> BuiltinAggArrayJsonStrip
834
- MTGeoJSON -> BuiltinAggGeoJson
835
- MTTextCSV -> BuiltinAggCsv
836
- MTAny -> BuiltinAggJson
837
- MTOpenAPI -> BuiltinAggJson
838
- MTUrlEncoded -> NoAgg -- TODO: unreachable since a previous step (producedMediaTypes) whitelists the media types that can become aggregates.
839
-
840
- -- Doing `Accept: application/vnd.pgrst.plan; for="application/vnd.pgrst.plan"` doesn't make sense, so we just empty the body.
841
- -- TODO: fail instead to be more strict
842
- MTPlan (MTPlan {}) _ _ -> NoAgg
843
- MTPlan media _ _ -> mediaToAggregate media apiReq
844
- _ -> NoAgg
845
- where
846
- noAgg = case act of
847
- ActionMutate _ -> rep == Just HeadersOnly || rep == Just None || isNothing rep
848
- ActionRead _isHead -> _isHead -- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
849
- ActionInvoke invMethod -> invMethod == InvHead
850
- _ -> False
851
-
852
834
-- | Do content negotiation. i.e. choose a media type based on the intersection of accepted/produced media types.
853
- negotiateContent :: AppConfig -> Action -> [MediaType ] -> Either ApiRequestError MediaType
854
- negotiateContent conf action accepts =
855
- case firstAcceptedPick of
856
- Just MTAny -> Right MTApplicationJSON -- by default(for */*) we respond with json
857
- Just mt -> Right mt
858
- Nothing -> Left . MediaTypeError $ map MediaType. toMime accepts
835
+ negotiateContent :: AppConfig -> ApiRequest -> QualifiedIdentifier -> [MediaType ] ->
836
+ HM. HashMap (RelIdentifier , MediaType ) MediaHandler -> Either ApiRequestError (MediaHandler , MediaType )
837
+ negotiateContent conf ApiRequest {iAction= act, iPreferences= Preferences {preferRepresentation= rep}} identifier accepts produces =
838
+ mtAnyToJSON $ case (act, firstAcceptedPick) of
839
+ (_, Nothing ) -> Left . MediaTypeError $ map MediaType. toMime accepts
840
+ (ActionMutate _, Just (x, mt)) -> Right (if rep == Just Full then x else NoAgg , mt)
841
+ -- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
842
+ -- TODO: despite no aggregate, these are responding with a Content-Type, which is not correct.
843
+ (ActionRead True , Just (_, mt)) -> Right (NoAgg , mt)
844
+ (ActionInvoke InvHead , Just (_, mt)) -> Right (NoAgg , mt)
845
+ (_, Just (x, mt)) -> Right (x, mt)
859
846
where
847
+ -- TODO initial */* is not overridable
848
+ -- initial handlers in the schema cache have a */* to BuiltinAggJson but they don't preserve the media type (application/json)
849
+ -- for now we just convert the resultant */* to application/json here
850
+ mtAnyToJSON = mapRight (\ (x, y) -> (x, if y == MTAny then MTApplicationJSON else y))
860
851
-- if there are multiple accepted media types, pick the first
861
- firstAcceptedPick = listToMaybe $ L. intersect accepts $ producedMediaTypes conf action
862
-
863
- producedMediaTypes :: AppConfig -> Action -> [MediaType ]
864
- producedMediaTypes conf action =
865
- case action of
866
- ActionRead _ -> defaultMediaTypes
867
- ActionInvoke _ -> defaultMediaTypes
868
- ActionInfo -> defaultMediaTypes
869
- ActionMutate _ -> defaultMediaTypes
870
- ActionInspect _ -> inspectMediaTypes
871
- where
872
- inspectMediaTypes = [MTOpenAPI , MTApplicationJSON , MTArrayJSONStrip , MTAny ]
873
- defaultMediaTypes =
874
- [MTApplicationJSON , MTArrayJSONStrip , MTSingularJSON True , MTSingularJSON False , MTGeoJSON , MTTextCSV ] ++
875
- [MTPlan MTApplicationJSON PlanText mempty | configDbPlanEnabled conf] ++ [MTAny ]
852
+ firstAcceptedPick = listToMaybe $ mapMaybe searchMT accepts
853
+ lookupIdent mt = -- first search for an aggregate that applies to the particular relation, then for one that applies to anyelement
854
+ HM. lookup (RelId identifier, mt) produces <|> HM. lookup (RelAnyElement , mt) produces
855
+ searchMT mt = case mt of
856
+ -- all the vendored media types have special handling as they have media type parameters, they cannot be overridden
857
+ m@ (MTVndSingularJSON strip) -> Just (BuiltinAggSingleJson strip, m)
858
+ m@ MTVndArrayJSONStrip -> Just (BuiltinAggArrayJsonStrip , m)
859
+ m@ (MTVndPlan (MTVndSingularJSON strip) _ _) -> mtPlanToNothing $ Just (BuiltinAggSingleJson strip, m)
860
+ m@ (MTVndPlan MTVndArrayJSONStrip _ _) -> mtPlanToNothing $ Just (BuiltinAggArrayJsonStrip , m)
861
+ -- all the other media types can be overridden
862
+ m@ (MTVndPlan mType _ _) -> mtPlanToNothing $ (,) <$> lookupIdent mType <*> pure m
863
+ x -> (,) <$> lookupIdent x <*> pure x
864
+ mtPlanToNothing x = if configDbPlanEnabled conf then x else Nothing -- don't find anything if the plan media type is not allowed
0 commit comments