fix: Dump media handlers and timezones with --dump-schema

Those were left out of the schema dump when the features were introduced, probably
because ByteString doesn't have a toJSON instance. Changing the type to Text solves
this easily.

Resolves #3237
This commit is contained in:
Wolfgang Walther
2024-02-20 18:44:17 +01:00
committed by Wolfgang Walther
parent 6d506df6f3
commit 8a21a9c34e
9 changed files with 1803 additions and 14 deletions
+1
View File
@@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #3205, Fix wrong subquery error returning a status of 400 Bad Request - @steve-chavez - #3205, Fix wrong subquery error returning a status of 400 Bad Request - @steve-chavez
- #3224, Return status code 406 for non-accepted media type instead of code 415 - @wolfgangwalther - #3224, Return status code 406 for non-accepted media type instead of code 415 - @wolfgangwalther
- #3160, Fix using select= query parameter for custom media type handlers - @wolfgangwalther - #3160, Fix using select= query parameter for custom media type handlers - @wolfgangwalther
- #3237, Dump media handlers and timezones with --dump-schema - @wolfgangwalther
### Deprecated ### Deprecated
+1
View File
@@ -67,6 +67,7 @@ let
ps.pyyaml ps.pyyaml
ps.requests ps.requests
ps.requests-unixsocket ps.requests-unixsocket
ps.syrupy
]); ]);
testIO = testIO =
+1 -1
View File
@@ -163,7 +163,7 @@ fromHeaders allowTxDbOverride acceptedTzNames headers =
listStripPrefix prefix prefList = listToMaybe $ mapMaybe (BS.stripPrefix prefix) prefList listStripPrefix prefix prefList = listToMaybe $ mapMaybe (BS.stripPrefix prefix) prefList
timezonePref = listStripPrefix "timezone=" prefs timezonePref = listStripPrefix "timezone=" prefs
isTimezonePrefAccepted = (S.member <$> timezonePref <*> pure acceptedTzNames) == Just True isTimezonePrefAccepted = (S.member <$> (decodeUtf8 <$> timezonePref) <*> pure acceptedTzNames) == Just True
maxAffectedPref = listStripPrefix "max-affected=" prefs >>= readMaybe . BS.unpack maxAffectedPref = listStripPrefix "max-affected=" prefs >>= readMaybe . BS.unpack
+1 -1
View File
@@ -30,7 +30,7 @@ import Protolude
type RoleSettings = (HM.HashMap ByteString (HM.HashMap ByteString ByteString)) type RoleSettings = (HM.HashMap ByteString (HM.HashMap ByteString ByteString))
type RoleIsolationLvl = HM.HashMap ByteString SQL.IsolationLevel type RoleIsolationLvl = HM.HashMap ByteString SQL.IsolationLevel
type TimezoneNames = Set ByteString -- cache timezone names for prefer timezone= type TimezoneNames = Set Text -- cache timezone names for prefer timezone=
toIsolationLevel :: (Eq a, IsString a) => a -> SQL.IsolationLevel toIsolationLevel :: (Eq a, IsString a) => a -> SQL.IsolationLevel
toIsolationLevel a = case a of toIsolationLevel a = case a of
+8 -6
View File
@@ -1,3 +1,4 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
@@ -10,6 +11,7 @@ module PostgREST.MediaType
, decodeMediaType , decodeMediaType
) where ) where
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS (c2w) import qualified Data.ByteString.Internal as BS (c2w)
@@ -28,23 +30,23 @@ data MediaType
| MTUrlEncoded | MTUrlEncoded
| MTOctetStream | MTOctetStream
| MTAny | MTAny
| MTOther ByteString | MTOther Text
-- vendored media types -- vendored media types
| MTVndArrayJSONStrip | MTVndArrayJSONStrip
| MTVndSingularJSON Bool | MTVndSingularJSON Bool
-- TODO MTVndPlan should only have its options as [Text]. Its ResultAggregate should have the typed attributes. -- TODO MTVndPlan should only have its options as [Text]. Its ResultAggregate should have the typed attributes.
| MTVndPlan MediaType MTVndPlanFormat [MTVndPlanOption] | MTVndPlan MediaType MTVndPlanFormat [MTVndPlanOption]
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic, JSON.ToJSON)
instance Hashable MediaType instance Hashable MediaType
data MTVndPlanOption data MTVndPlanOption
= PlanAnalyze | PlanVerbose | PlanSettings | PlanBuffers | PlanWAL = PlanAnalyze | PlanVerbose | PlanSettings | PlanBuffers | PlanWAL
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic, JSON.ToJSON)
instance Hashable MTVndPlanOption instance Hashable MTVndPlanOption
data MTVndPlanFormat data MTVndPlanFormat
= PlanJSON | PlanText = PlanJSON | PlanText
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic, JSON.ToJSON)
instance Hashable MTVndPlanFormat instance Hashable MTVndPlanFormat
-- | Convert MediaType to a Content-Type HTTP Header -- | Convert MediaType to a Content-Type HTTP Header
@@ -70,7 +72,7 @@ toMime (MTVndSingularJSON False) = "application/vnd.pgrst.object+json"
toMime MTUrlEncoded = "application/x-www-form-urlencoded" toMime MTUrlEncoded = "application/x-www-form-urlencoded"
toMime MTOctetStream = "application/octet-stream" toMime MTOctetStream = "application/octet-stream"
toMime MTAny = "*/*" toMime MTAny = "*/*"
toMime (MTOther ct) = ct toMime (MTOther ct) = encodeUtf8 ct
toMime (MTVndPlan mt fmt opts) = toMime (MTVndPlan mt fmt opts) =
"application/vnd.pgrst.plan+" <> toMimePlanFormat fmt <> "application/vnd.pgrst.plan+" <> toMimePlanFormat fmt <>
("; for=\"" <> toMime mt <> "\"") <> ("; for=\"" <> toMime mt <> "\"") <>
@@ -132,7 +134,7 @@ decodeMediaType mt =
"application/vnd.pgrst.array+json":rest -> checkArrayNullStrip rest "application/vnd.pgrst.array+json":rest -> checkArrayNullStrip rest
"application/vnd.pgrst.array":rest -> checkArrayNullStrip rest "application/vnd.pgrst.array":rest -> checkArrayNullStrip rest
"*/*":_ -> MTAny "*/*":_ -> MTAny
other:_ -> MTOther other other:_ -> MTOther $ decodeUtf8 other
_ -> MTAny _ -> MTAny
where where
checkArrayNullStrip ["nulls=stripped"] = MTVndArrayJSONStrip checkArrayNullStrip ["nulls=stripped"] = MTVndArrayJSONStrip
+4 -5
View File
@@ -31,7 +31,6 @@ import Control.Monad.Extra (whenJust)
import Data.Aeson ((.=)) import Data.Aeson ((.=))
import qualified Data.Aeson as JSON import qualified Data.Aeson as JSON
import qualified Data.Aeson.Types as JSON
import qualified Data.HashMap.Strict as HM import qualified Data.HashMap.Strict as HM
import qualified Data.HashMap.Strict.InsOrd as HMI import qualified Data.HashMap.Strict.InsOrd as HMI
import qualified Data.Set as S import qualified Data.Set as S
@@ -86,13 +85,13 @@ data SchemaCache = SchemaCache
} }
instance JSON.ToJSON SchemaCache where instance JSON.ToJSON SchemaCache where
toJSON (SchemaCache tabs rels routs reps _ _) = JSON.object [ toJSON (SchemaCache tabs rels routs reps hdlers tzs) = JSON.object [
"dbTables" .= JSON.toJSON tabs "dbTables" .= JSON.toJSON tabs
, "dbRelationships" .= JSON.toJSON rels , "dbRelationships" .= JSON.toJSON rels
, "dbRoutines" .= JSON.toJSON routs , "dbRoutines" .= JSON.toJSON routs
, "dbRepresentations" .= JSON.toJSON reps , "dbRepresentations" .= JSON.toJSON reps
, "dbMediaHandlers" .= JSON.emptyArray , "dbMediaHandlers" .= JSON.toJSON hdlers
, "dbTimezones" .= JSON.emptyArray , "dbTimezones" .= JSON.toJSON tzs
] ]
showSummary :: SchemaCache -> Text showSummary :: SchemaCache -> Text
@@ -1222,7 +1221,7 @@ timezones = SQL.Statement sql HE.noParams decodeTimezones
where where
sql = "SELECT name FROM pg_timezone_names" sql = "SELECT name FROM pg_timezone_names"
decodeTimezones :: HD.Result TimezoneNames decodeTimezones :: HD.Result TimezoneNames
decodeTimezones = S.fromList . map encodeUtf8 <$> HD.rowList (column HD.text) decodeTimezones = S.fromList <$> HD.rowList (column HD.text)
param :: HE.Value a -> HE.Params a param :: HE.Value a -> HE.Params a
param = HE.param . HE.nonNullable param = HE.param . HE.nonNullable
+1 -1
View File
@@ -110,7 +110,7 @@ data MediaHandler
-- custom -- custom
| CustomFunc QualifiedIdentifier RelIdentifier | CustomFunc QualifiedIdentifier RelIdentifier
| NoAgg | NoAgg
deriving (Eq, Show) deriving (Eq, Show, Generic, JSON.ToJSON)
funcReturnsSingle :: Routine -> Bool funcReturnsSingle :: Routine -> Bool
funcReturnsSingle proc = case proc of funcReturnsSingle proc = case proc of
File diff suppressed because it is too large Load Diff
+19
View File
@@ -21,11 +21,21 @@ import jwt
import pytest import pytest
import requests import requests
import requests_unixsocket import requests_unixsocket
from syrupy.extensions.json import SingleFileSnapshotExtension
import yaml import yaml
from config import * from config import *
class YamlSnapshotExtension(SingleFileSnapshotExtension):
_file_extension = "yaml"
@pytest.fixture
def snapshot_yaml(snapshot):
return snapshot.use_extension(YamlSnapshotExtension)
def itemgetter(*items): def itemgetter(*items):
"operator.itemgetter with None as fallback when key does not exist" "operator.itemgetter with None as fallback when key does not exist"
if len(items) == 1: if len(items) == 1:
@@ -225,3 +235,12 @@ def test_invalid_openapi_mode(invalidopenapimodes, defaultenv):
for line in dump.split("\n"): for line in dump.split("\n"):
if line.startswith("openapi-mode"): if line.startswith("openapi-mode"):
print(line) print(line)
# If this test is failing, run pytest with --snapshot-update.
def test_schema_cache_snapshot(baseenv, snapshot_yaml):
"Dump of schema cache should match snapshot."
schema_cache = yaml.load(cli(["--dump-schema"], env=baseenv), Loader=yaml.Loader)
formatted = yaml.dump(schema_cache, encoding="utf8", allow_unicode=True)
assert formatted == snapshot_yaml