diff --git a/cabal.project b/cabal.project index b1c4c70161c..ddfd0b8efc7 100644 --- a/cabal.project +++ b/cabal.project @@ -1,4 +1,4 @@ -with-compiler: ghc-8.10.7 +with-compiler: ghc-9.2.4 packages: libs/api-bot/ diff --git a/docs/src/developer/developer/upgrading.md b/docs/src/developer/developer/upgrading.md new file mode 100644 index 00000000000..74a7d40bd27 --- /dev/null +++ b/docs/src/developer/developer/upgrading.md @@ -0,0 +1,32 @@ +# Upgrading + +Here are some workflow suggestions when you're trying to upgrade packages (or GHC) in wire-server. + +- Use and install https://github.com/maralorn/nix-output-monitor when building nix derivations locally. It shows you the dependency tree building as well as the progress. This can give you a feeling how much progress you've made. + +- The `.envrc` environment probably won't load anymore. To fix this adjust the for packages that get passed to `shellFor` in `wire-server.nix`. In the beginning you might want to start out with an empty list as an argument. + +- You can explore our nix derivations via `nix repl` and then `:l ./nix` to load the attrset in `./nix/default.nix`. For example to see which version of `aeson` will be used you can evaluate `wireServer.haskellPackages.aeson.version`. TAB-autocompletion also tells you if multiple versions of a package are included in the `nixpkgs` pin, e.g. `aeson_1_5_6_0`, `aeson_2_1_1_0`, which can be used in `manual-overrides.nix` to overwrite the default, e.g. `aeson`. + +- Your goal is to make all packages compile again with nix. Start small by trying to build single packages, e.g. `wire-api` or any external dependencies. +``` +nix-build ./nix -A wireServer.haskellPackagesUnoptimizedNoDocs.wire-api +``` + +- When a dependency doesn't build anymore because of unmet version constraints see if you can use a never version (`haskell-pins.nix`, or `manual-overrides.nix` if multiple versions of the package are included in the `nixpkgs` pin). Also check out the git repo or issue tracker of the dependency to find forks that work. You can also try removing versions constraints of packages: see `doJailbreak` in `manual-overrides.nix`. + +- If test-suites of a dependency don't compile, or cannot be run because they require IO interaction you can disable them: see `dontCheck` in `manual-overrides.nix` + +- To force a rebuild of the nix shell delete the `.env` directory and run `direnv reload` + +- If you need to fix code in a dependency consider forking it and creating a PR to the upstream after successful integration. Clone the repo and then symlink it inside `libs/` then run `generate-local-nix-packages.sh` to temporarily include its dependencies in the development shell. Make sure to include the package in `shellFor`. If you've got a working shell you can check the output of `ghc-pkg dump` to see the list of nix-provided ghc packages used by cabal. + +- Consider using `ghcid --command "cabal repl "` when fixing packages + +- Delete the `dist-newstyle` directory everytime you upgrade the nix environment, without any exception. That is because cabal does not pick up changes to the `ghc-pkg` set (defined the development shell). Cabal will complain about missing dependencies in these cases. + +- When trying to build any packages with cabal (e.g. for fixing code in a depencency or fixing local packages), make sure you've got the right package set in the `shellFor` argument and the right transitive dependencies in `cabal.project`. It takes a couple of tries to get both: a nix provided environment that works, and cabal not complaining about missing dependencies when building inside the environment. + +- It might happen that a package's test suite dependencies are not available in the nix environment. When you try to build with cabal it might try to build these external dependencies (which you want to avoid). What might work in these cases is to temporarily update the `default.nix` file (generated by `generate-local-nix-packages.sh`) to add the test suits dependencies to the library section. + +- If cabal is complaining about a missing `libsodium`, add `sodium-crypt-sign` to the `shellFor` environment. diff --git a/hack/bin/generate-local-nix-packages.sh b/hack/bin/generate-local-nix-packages.sh index abfdfeb9251..c993e71188c 100755 --- a/hack/bin/generate-local-nix-packages.sh +++ b/hack/bin/generate-local-nix-packages.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ROOT_DIR=$(cd -- "$SCRIPT_DIR/../../" &> /dev/null && pwd) -cabalFiles=$(find "$ROOT_DIR" -name '*.cabal' \ +cabalFiles=$(find -L "$ROOT_DIR" -name '*.cabal' \ | grep -v dist-newstyle | sort) warningFile=$(mktemp) @@ -21,7 +21,7 @@ echo "$cabalFiles" \ # shellcheck disable=SC2016 echo "$cabalFiles" \ - | xargs -I {} bash -c 'cd $(dirname {}); cabal2nix . --no-hpack --extra-arguments gitignoreSource | sed "s/.\/./gitignoreSource .\/./g" >> default.nix; nixpkgs-fmt default.nix &> /dev/null' + | xargs -I {} bash -c 'cd $(dirname {}); cabal2nix . --no-hpack --extra-arguments gitignoreSource | sed "s/src = \.\/\./src = gitignoreSource .\/./g" >> default.nix; nixpkgs-fmt default.nix &> /dev/null' overridesFile="$ROOT_DIR/nix/local-haskell-packages.nix" diff --git a/libs/extended/src/Servant/API/Extended.hs b/libs/extended/src/Servant/API/Extended.hs index f87fc5d2146..2d6914c7f11 100644 --- a/libs/extended/src/Servant/API/Extended.hs +++ b/libs/extended/src/Servant/API/Extended.hs @@ -21,6 +21,7 @@ module Servant.API.Extended where import qualified Data.ByteString.Lazy as BL import Data.EitherR (fmapL) +import Data.Kind import Data.Metrics.Servant import Data.String.Conversions (cs) import Data.Typeable @@ -54,12 +55,12 @@ import Prelude () -- that'll be). -- -- See also: https://github.com/haskell-servant/servant/issues/353 -data ReqBodyCustomError' (mods :: [*]) (list :: [ct]) (tag :: Symbol) (a :: *) +data ReqBodyCustomError' (mods :: [Type]) (list :: [ct]) (tag :: Symbol) (a :: Type) type ReqBodyCustomError = ReqBodyCustomError' '[Required, Strict] -- | Custom parse error for bad request bodies. -class MakeCustomError (tag :: Symbol) (a :: *) where +class MakeCustomError (tag :: Symbol) (a :: Type) where makeCustomError :: String -> ServerError -- | Variant of the 'ReqBody'' instance that takes a 'ServerError' as argument instead of a diff --git a/libs/hscim/default.nix b/libs/hscim/default.nix index ff9dda29550..7bd207e2730 100644 --- a/libs/hscim/default.nix +++ b/libs/hscim/default.nix @@ -174,7 +174,7 @@ mkDerivation { warp ]; testToolDepends = [ hspec-discover ]; - homepage = "httpsgitignoreSource ./.github.cogitignoreSource ./.ireapgitignoreSource ./.ire-servegitignoreSource ./.ibgitignoreSource ./.scigitignoreSource ./.EADME.md"; + homepage = "https://github.com/wireapp/wire-server/libs/hscim/README.md"; description = "hscim json schema and server implementation"; license = lib.licenses.agpl3Only; mainProgram = "hscim-server"; diff --git a/libs/hscim/hscim.cabal b/libs/hscim/hscim.cabal index 6ae8cbdae80..f98ad510a3d 100644 --- a/libs/hscim/hscim.cabal +++ b/libs/hscim/hscim.cabal @@ -83,44 +83,44 @@ library ghc-options: -Wall -Werror build-depends: - aeson >=2 - , aeson-qq >=0.8.2 && <0.9 - , attoparsec >=0.13.2 && <0.15 - , base >=4.12 && <4.15 - , bytestring >=0.10.8 && <0.11 - , case-insensitive >=1.2.1.0 && <1.3 - , email-validate >=2.3.2 && <2.4 - , hashable >=1.2.7 && <1.5 - , hedgehog >=1.0.1 && <1.1 - , hspec >=2.7.1 && <2.9 - , hspec-expectations >=0.8.2 && <0.9 - , hspec-wai >=0.9.2 && <0.10 - , http-api-data >=0.4.1 && <0.5 - , http-media >=0.8.0 && <0.9 - , http-types >=0.12.3 && <0.13 - , hw-hspec-hedgehog >=0.1.0 && <0.2 - , list-t >=1.0.4 && <1.1 - , microlens >=0.4.10 && <0.5 - , mmorph >=1.1.3 && <1.2 - , mtl >=2.2.2 && <2.3 - , network-uri >=2.6.2 && <2.7 - , retry >=0.8.1.0 && <0.10 - , scientific >=0.3.6 && <0.4 - , servant >=0.16.2 && <0.20 - , servant-client >=0.16.2 && <0.20 - , servant-client-core >=0.16.2 && <0.20 - , servant-server >=0.16.2 && <0.20 - , stm >=2.5.0 && <2.6 - , stm-containers >=1.1.0 && <1.3 - , string-conversions >=0.4.0 && <0.5 - , template-haskell >=2.14.0 && <2.17 - , text >=1.2.3 && <1.3 - , time >=1.8.0 && <1.10 - , unordered-containers >=0.2.10 && <0.3 - , uuid >=1.3.13 && <1.4 - , wai >=3.2.2 && <3.3 - , wai-extra >=3.0.28 && <3.2 - , warp >=3.2.28 && <3.4 + aeson + , aeson-qq + , attoparsec + , base + , bytestring + , case-insensitive + , email-validate + , hashable + , hedgehog + , hspec + , hspec-expectations + , hspec-wai + , http-api-data + , http-media + , http-types + , hw-hspec-hedgehog + , list-t + , microlens + , mmorph + , mtl + , network-uri + , retry + , scientific + , servant + , servant-client + , servant-client-core + , servant-server + , stm + , stm-containers + , string-conversions + , template-haskell + , text + , time + , unordered-containers + , uuid + , wai + , wai-extra + , warp default-language: Haskell2010 @@ -148,45 +148,45 @@ executable hscim-server ghc-options: -Wall -Werror -threaded -rtsopts -with-rtsopts=-N build-depends: - aeson >=2 - , aeson-qq >=0.8.2 && <0.9 - , attoparsec >=0.13.2 && <0.15 - , base >=4.12 && <4.15 - , bytestring >=0.10.8 && <0.11 - , case-insensitive >=1.2.1.0 && <1.3 - , email-validate >=2.3.2 && <2.4 - , hashable >=1.2.7 && <1.5 - , hedgehog >=1.0.1 && <1.1 + aeson + , aeson-qq + , attoparsec + , base + , bytestring + , case-insensitive + , email-validate + , hashable + , hedgehog , hscim - , hspec >=2.7.1 && <2.9 - , hspec-expectations >=0.8.2 && <0.9 - , hspec-wai >=0.9.2 && <0.10 - , http-api-data >=0.4.1 && <0.5 - , http-media >=0.8.0 && <0.9 - , http-types >=0.12.3 && <0.13 - , hw-hspec-hedgehog >=0.1.0 && <0.2 - , list-t >=1.0.4 && <1.1 - , microlens >=0.4.10 && <0.5 - , mmorph >=1.1.3 && <1.2 - , mtl >=2.2.2 && <2.3 - , network-uri >=2.6.2 && <2.7 - , retry >=0.8.1.0 && <0.10 - , scientific >=0.3.6 && <0.4 - , servant >=0.16.2 && <0.20 - , servant-client >=0.16.2 && <0.20 - , servant-client-core >=0.16.2 && <0.20 - , servant-server >=0.16.2 && <0.20 - , stm >=2.5.0 && <2.6 - , stm-containers >=1.1.0 && <1.3 - , string-conversions >=0.4.0 && <0.5 - , template-haskell >=2.14.0 && <2.17 - , text >=1.2.3 && <1.3 - , time >=1.8.0 && <1.10 - , unordered-containers >=0.2.10 && <0.3 - , uuid >=1.3.13 && <1.4 - , wai >=3.2.2 && <3.3 - , wai-extra >=3.0.28 && <3.2 - , warp >=3.2.28 && <3.4 + , hspec + , hspec-expectations + , hspec-wai + , http-api-data + , http-media + , http-types + , hw-hspec-hedgehog + , list-t + , microlens + , mmorph + , mtl + , network-uri + , retry + , scientific + , servant + , servant-client + , servant-client-core + , servant-server + , stm + , stm-containers + , string-conversions + , template-haskell + , text + , time + , unordered-containers + , uuid + , wai + , wai-extra + , warp default-language: Haskell2010 @@ -232,45 +232,45 @@ test-suite spec ghc-options: -Wall -Werror -threaded -rtsopts -with-rtsopts=-N build-tool-depends: hspec-discover:hspec-discover build-depends: - aeson >=2 - , aeson-qq >=0.8.2 && <0.9 - , attoparsec >=0.13.2 && <0.15 - , base >=4.12 && <4.15 - , bytestring >=0.10.8 && <0.11 - , case-insensitive >=1.2.1.0 && <1.3 - , email-validate >=2.3.2 && <2.4 - , hashable >=1.2.7 && <1.5 - , hedgehog >=1.0.1 && <1.1 + aeson + , aeson-qq + , attoparsec + , base + , bytestring + , case-insensitive + , email-validate + , hashable + , hedgehog , hscim - , hspec >=2.7.1 && <2.9 - , hspec-expectations >=0.8.2 && <0.9 - , hspec-wai >=0.9.2 && <0.10 - , http-api-data >=0.4.1 && <0.5 - , http-media >=0.8.0 && <0.9 - , http-types >=0.12.3 && <0.13 - , hw-hspec-hedgehog >=0.1.0 && <0.2 + , hspec + , hspec-expectations + , hspec-wai + , http-api-data + , http-media + , http-types + , hw-hspec-hedgehog , indexed-traversable - , list-t >=1.0.4 && <1.1 - , microlens >=0.4.10 && <0.5 - , mmorph >=1.1.3 && <1.2 - , mtl >=2.2.2 && <2.3 - , network-uri >=2.6.2 && <2.7 - , retry >=0.8.1.0 && <0.10 - , scientific >=0.3.6 && <0.4 - , servant >=0.16.2 && <0.20 - , servant-client >=0.16.2 && <0.20 - , servant-client-core >=0.16.2 && <0.20 - , servant-server >=0.16.2 && <0.20 - , stm >=2.5.0 && <2.6 - , stm-containers >=1.1.0 && <1.3 - , string-conversions >=0.4.0 && <0.5 - , template-haskell >=2.14.0 && <2.17 - , text >=1.2.3 && <1.3 - , time >=1.8.0 && <1.10 - , unordered-containers >=0.2.10 && <0.3 - , uuid >=1.3.13 && <1.4 - , wai >=3.2.2 && <3.3 - , wai-extra >=3.0.28 && <3.2 - , warp >=3.2.28 && <3.4 + , list-t + , microlens + , mmorph + , mtl + , network-uri + , retry + , scientific + , servant + , servant-client + , servant-client-core + , servant-server + , stm + , stm-containers + , string-conversions + , template-haskell + , text + , time + , unordered-containers + , uuid + , wai + , wai-extra + , warp default-language: Haskell2010 diff --git a/libs/hscim/src/Web/Scim/Test/Util.hs b/libs/hscim/src/Web/Scim/Test/Util.hs index e05dfad1570..754e2053ef2 100644 --- a/libs/hscim/src/Web/Scim/Test/Util.hs +++ b/libs/hscim/src/Web/Scim/Test/Util.hs @@ -82,17 +82,17 @@ import Web.Scim.Schema.User (UserTypes (..)) -- FUTUREWORK: make this a PR upstream. (while we're at it, we can also patch 'WaiSession' -- and 'request' to keep track of the 'SRequest', and add that to the error message here with -- the response.) -shouldRespondWith :: HasCallStack => WaiSession SResponse -> ResponseMatcher -> WaiExpectation +shouldRespondWith :: HasCallStack => WaiSession st SResponse -> ResponseMatcher -> WaiExpectation st shouldRespondWith action matcher = either (liftIO . expectationFailure) pure =<< doesRespondWith action matcher -doesRespondWith :: HasCallStack => WaiSession SResponse -> ResponseMatcher -> WaiSession (Either String ()) +doesRespondWith :: HasCallStack => WaiSession st SResponse -> ResponseMatcher -> WaiSession st (Either String ()) doesRespondWith action matcher = do r <- action let extmsg = " details: " <> show r <> "\n" pure $ maybe (Right ()) (Left . (<> extmsg)) (match r matcher) -shouldEventuallyRespondWith :: HasCallStack => WaiSession SResponse -> ResponseMatcher -> WaiExpectation +shouldEventuallyRespondWith :: HasCallStack => WaiSession st SResponse -> ResponseMatcher -> WaiExpectation st shouldEventuallyRespondWith action matcher = either (liftIO . expectationFailure) pure =<< Retry.retrying @@ -150,31 +150,31 @@ defAcceptanceQueryConfig = AcceptanceQueryConfig {..} a' = maybe a (\(t, l) -> if l == '/' then t else a) $ BS8.unsnoc a b' = maybe b (\(h, t) -> if h == '/' then t else b) $ BS8.uncons b -post :: ByteString -> L.ByteString -> WaiSession SResponse +post :: ByteString -> L.ByteString -> WaiSession st SResponse post path = request methodPost path [(hContentType, "application/scim+json")] -put :: ByteString -> L.ByteString -> WaiSession SResponse +put :: ByteString -> L.ByteString -> WaiSession st SResponse put path = request methodPut path [(hContentType, "application/scim+json")] -patch :: ByteString -> L.ByteString -> WaiSession SResponse +patch :: ByteString -> L.ByteString -> WaiSession st SResponse patch path = request methodPatch path [(hContentType, "application/scim+json")] -request' :: Method -> AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession SResponse +request' :: Method -> AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession st SResponse request' method (AcceptanceQueryConfig prefix token) path = request method (prefix path) [(hAuthorization, token), (hContentType, "application/scim+json")] -get' :: AcceptanceQueryConfig tag -> ByteString -> WaiSession SResponse +get' :: AcceptanceQueryConfig tag -> ByteString -> WaiSession st SResponse get' cfg path = request' methodGet cfg path "" -post' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession SResponse +post' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession st SResponse post' = request' methodPost -put' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession SResponse +put' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession st SResponse put' = request' methodPut -patch' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession SResponse +patch' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession st SResponse patch' = request' methodPatch -delete' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession SResponse +delete' :: AcceptanceQueryConfig tag -> ByteString -> L.ByteString -> WaiSession st SResponse delete' = request' methodDelete ---------------------------------------------------------------------------- diff --git a/libs/hscim/test/Test/Capabilities/MetaSchemaSpec.hs b/libs/hscim/test/Test/Capabilities/MetaSchemaSpec.hs index bff1aa4068a..0c846782f64 100644 --- a/libs/hscim/test/Test/Capabilities/MetaSchemaSpec.hs +++ b/libs/hscim/test/Test/Capabilities/MetaSchemaSpec.hs @@ -44,9 +44,9 @@ app = do shouldSatisfy :: (Show a, FromJSON a) => - WaiSession SResponse -> + WaiSession st SResponse -> (a -> Bool) -> - WaiExpectation + WaiExpectation st shouldSatisfy resp predicate = do maybeDecoded <- eitherDecode . simpleBody <$> resp case maybeDecoded of @@ -69,7 +69,7 @@ coreSchemas = ] spec :: Spec -spec = beforeAll app $ do +spec = with app $ do describe "GET /Schemas" $ do it "lists schemas" $ do get "/Schemas" `shouldRespondWith` 200 diff --git a/libs/hscim/test/Test/Class/AuthSpec.hs b/libs/hscim/test/Test/Class/AuthSpec.hs index a5130476d57..9f5b0632715 100644 --- a/libs/hscim/test/Test/Class/AuthSpec.hs +++ b/libs/hscim/test/Test/Class/AuthSpec.hs @@ -35,7 +35,7 @@ testStorage :: IO TestStorage testStorage = TestStorage <$> STMMap.newIO <*> STMMap.newIO spec :: Spec -spec = beforeAll ((\s -> app @Mock empty (nt s)) <$> testStorage) $ do +spec = with ((\s -> app @Mock empty (nt s)) <$> testStorage) $ do describe "/ServiceProviderConfig" $ do it "is accessible without authentication" $ do get "/ServiceProviderConfig" `shouldRespondWith` 200 diff --git a/libs/hscim/test/Test/Class/GroupSpec.hs b/libs/hscim/test/Test/Class/GroupSpec.hs index 7d3a472854b..a48f92aa3cf 100644 --- a/libs/hscim/test/Test/Class/GroupSpec.hs +++ b/libs/hscim/test/Test/Class/GroupSpec.hs @@ -43,7 +43,7 @@ app = do (nt storage) spec :: Spec -spec = beforeAll app $ do +spec = with app $ do describe "GET & POST /Groups" $ do it "responds with [] in empty environment" $ do get "/" `shouldRespondWith` emptyList @@ -54,17 +54,21 @@ spec = beforeAll app $ do it "responds with 404 for unknown group" $ do get "/9999" `shouldRespondWith` 404 it "retrieves stored group" $ do + post "/" adminGroup `shouldRespondWith` 201 -- the test implementation stores groups with uid [0,1..n-1] get "/0" `shouldRespondWith` admins describe "PUT /Groups/:id" $ do it "adds member to existing group" $ do + post "/" adminGroup `shouldRespondWith` 201 put "/0" adminUpdate0 `shouldRespondWith` updatedAdmins0 it "does not create new group" $ do put "/9999" adminGroup `shouldRespondWith` 404 describe "DELETE /Groups/:id" $ do it "responds with 404 for unknown group" $ do + post "/" adminGroup `shouldRespondWith` 201 delete "/Users/unknown" `shouldRespondWith` 404 it "deletes a stored group" $ do + post "/" adminGroup `shouldRespondWith` 201 delete "/0" `shouldRespondWith` 204 -- group should be gone get "/0" `shouldRespondWith` 404 diff --git a/libs/hscim/test/Test/Class/UserSpec.hs b/libs/hscim/test/Test/Class/UserSpec.hs index deebc048406..3d3d16d0e17 100644 --- a/libs/hscim/test/Test/Class/UserSpec.hs +++ b/libs/hscim/test/Test/Class/UserSpec.hs @@ -39,7 +39,7 @@ app = do pure $ mkapp @Mock (Proxy @(UserAPI Mock)) (toServant (userServer auth)) (nt storage) spec :: Spec -spec = beforeAll app $ do +spec = with app $ do describe "GET & POST /Users" $ do it "responds with [] in empty environment" $ do get "/" `shouldRespondWith` emptyList @@ -49,17 +49,23 @@ spec = beforeAll app $ do get "/" `shouldRespondWith` allUsers describe "filtering" $ do it "can filter by username" $ do + post "/" newBarbara `shouldRespondWith` 201 get "/?filter=userName eq \"bjensen\"" `shouldRespondWith` onlyBarbara it "is case-insensitive regarding syntax" $ do + post "/" newBarbara `shouldRespondWith` 201 get "/?filter=USERName EQ \"bjensen\"" `shouldRespondWith` onlyBarbara it "is case-insensitive regarding usernames" $ do + post "/" newBarbara `shouldRespondWith` 201 get "/?filter=userName eq \"BJensen\"" `shouldRespondWith` onlyBarbara it "handles malformed filter syntax" $ do + post "/" newBarbara `shouldRespondWith` 201 get "/?filter=userName eqq \"bjensen\"" `shouldRespondWith` 400 -- TODO: would be nice to check the error message as well it "handles type errors in comparisons" $ do + post "/" newBarbara `shouldRespondWith` 201 get "/?filter=userName eq true" `shouldRespondWith` 400 + describe "GET /Users/:id" $ do it "responds with 404 for unknown user" $ do get "/9999" `shouldRespondWith` 404 @@ -68,12 +74,15 @@ spec = beforeAll app $ do xit "responds with 401 for unparseable user ID" $ do get "/unparseable" `shouldRespondWith` 401 it "retrieves stored user" $ do + post "/" newBarbara `shouldRespondWith` 201 -- the test implementation stores users with uid [0,1..n-1] get "/0" `shouldRespondWith` barbara describe "PUT /Users/:id" $ do it "overwrites the user" $ do + post "/" newBarbara `shouldRespondWith` 201 put "/0" barbUpdate0 `shouldRespondWith` updatedBarb0 it "does not create new users" $ do + post "/" newBarbara `shouldRespondWith` 201 put "/9999" newBarbara `shouldRespondWith` 404 -- TODO(arianvp): Perhaps we want to make this an acceptance spec. describe "PATCH /Users/:id" $ do @@ -82,6 +91,7 @@ spec = beforeAll app $ do -- TODO(arianvp): We need to merge multi-value fields, but not supported yet -- TODO(arianvp): Add and Replace tests currently identical, because of lack of multi-value it "adds all fields if no target" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -116,6 +126,7 @@ spec = beforeAll app $ do { matchStatus = 200 } it "adds fields if they didn't exist yet" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -147,6 +158,7 @@ spec = beforeAll app $ do { matchStatus = 200 } it "replaces individual simple fields" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -183,6 +195,7 @@ spec = beforeAll app $ do -- not limit by type what fields it lenses in to. It is a very untyped -- thingy currently. it "PatchOp is atomic. Either fully applies or not at all" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -202,6 +215,7 @@ spec = beforeAll app $ do describe "Replace" $ do -- TODO(arianvp): Implement and test multi-value fields properly it "adds all fields if no target" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -236,6 +250,7 @@ spec = beforeAll app $ do { matchStatus = 200 } it "adds fields if they didn't exist yet" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -267,6 +282,7 @@ spec = beforeAll app $ do { matchStatus = 200 } it "replaces individual simple fields" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -298,6 +314,7 @@ spec = beforeAll app $ do { matchStatus = 200 } it "PatchOp is atomic. Either fully applies or not at all" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -316,6 +333,7 @@ spec = beforeAll app $ do get "/0" `shouldRespondWith` smallUserGet {matchStatus = 200} describe "Remove" $ do it "fails if no target" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" barbUpdate0 -- reset patch "/0" @@ -329,6 +347,7 @@ spec = beforeAll app $ do { matchStatus = 400 } it "fails if removing immutable" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" barbUpdate0 -- reset patch "/0" @@ -342,6 +361,7 @@ spec = beforeAll app $ do { matchStatus = 400 } it "deletes the specified attribute" $ do + post "/" newBarbara `shouldRespondWith` 201 _ <- put "/0" smallUser -- reset patch "/0" @@ -369,6 +389,7 @@ spec = beforeAll app $ do it "responds with 404 for unknown user" $ do delete "/9999" `shouldRespondWith` 404 it "deletes a stored user" $ do + post "/" newBarbara `shouldRespondWith` 201 delete "/0" `shouldRespondWith` 204 -- user should be gone get "/0" `shouldRespondWith` 404 diff --git a/libs/hscim/test/Test/Schema/UserSpec.hs b/libs/hscim/test/Test/Schema/UserSpec.hs index 6f7ae9180a6..deff894b70f 100644 --- a/libs/hscim/test/Test/Schema/UserSpec.hs +++ b/libs/hscim/test/Test/Schema/UserSpec.hs @@ -1,6 +1,7 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -- This file is part of the Wire Server implementation. -- diff --git a/libs/imports/src/Imports.hs b/libs/imports/src/Imports.hs index 81e583b880b..4f5747f4101 100644 --- a/libs/imports/src/Imports.hs +++ b/libs/imports/src/Imports.hs @@ -153,7 +153,7 @@ import Data.HashMap.Strict (HashMap) import Data.HashSet (HashSet) import Data.Int -- 'insert' and 'delete' are common in database modules -import Data.List hiding (delete, insert) +import Data.List hiding (delete, insert, singleton) -- Lazy and strict versions are the same import Data.Map (Map) import Data.Maybe @@ -161,7 +161,7 @@ import Data.Maybe import Data.Monoid hiding (First (..), Last (..)) import Data.Ord -- conflicts with Options.Applicative.Option (should we care?) -import Data.Semigroup hiding (Option, diff, option) +import Data.Semigroup hiding (diff) import Data.Set (Set) import Data.String import Data.Text (Text) diff --git a/libs/polysemy-wire-zoo/src/Wire/Sem/Paging.hs b/libs/polysemy-wire-zoo/src/Wire/Sem/Paging.hs index 2636eae7a96..9648b957dfd 100644 --- a/libs/polysemy-wire-zoo/src/Wire/Sem/Paging.hs +++ b/libs/polysemy-wire-zoo/src/Wire/Sem/Paging.hs @@ -30,13 +30,14 @@ module Wire.Sem.Paging ) where +import Data.Kind import Imports -type family Page p a :: (page :: *) | page -> p a +type family Page p a :: (page :: Type) | page -> p a -type family PagingState p a = (ps :: *) +type family PagingState p a = (ps :: Type) -type family PagingBounds p a :: * +type family PagingBounds p a :: Type class Paging p where pageItems :: Page p a -> [a] diff --git a/libs/types-common/default.nix b/libs/types-common/default.nix index b1f22221ba0..35c3fed38e0 100644 --- a/libs/types-common/default.nix +++ b/libs/types-common/default.nix @@ -41,7 +41,6 @@ , schema-profunctor , scientific , servant-server -, singletons , string-conversions , swagger , swagger2 @@ -100,7 +99,6 @@ mkDerivation { schema-profunctor scientific servant-server - singletons string-conversions swagger swagger2 diff --git a/libs/types-common/src/Data/Range.hs b/libs/types-common/src/Data/Range.hs index ffceb0fddf9..a9966edf35b 100644 --- a/libs/types-common/src/Data/Range.hs +++ b/libs/types-common/src/Data/Range.hs @@ -24,7 +24,6 @@ module Data.Range ( Range, toRange, - LTE, Within, Bounds (..), checked, @@ -58,8 +57,6 @@ where import Cassandra (ColumnType, Cql (..), Tagged, retag) import Control.Lens ((%~), (?~)) import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON)) -import Data.Aeson.Types as Aeson (Parser) -import qualified Data.Attoparsec.ByteString as Atto import qualified Data.Bifunctor as Bifunctor import qualified Data.ByteString as B import Data.ByteString.Conversion @@ -75,22 +72,20 @@ import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as N import Data.List1 (List1, toNonEmpty) import qualified Data.Map as Map +import Data.Proxy import Data.Schema import Data.Sequence (Seq) import qualified Data.Sequence as Seq import qualified Data.Set as Set -import Data.Singletons -import Data.Singletons.Prelude.Num -import Data.Singletons.Prelude.Ord -import Data.Singletons.TypeLits import Data.Swagger (ParamSchema, ToParamSchema (..)) import qualified Data.Swagger as S import qualified Data.Text as T import Data.Text.Ascii (AsciiChar, AsciiChars, AsciiText, fromAsciiChars) import qualified Data.Text.Ascii as Ascii import qualified Data.Text.Lazy as TL +import Data.Type.Ord +import GHC.TypeNats import Imports -import Numeric.Natural (Natural) import Servant (FromHttpApiData (..)) import System.Random (Random) import Test.QuickCheck (Arbitrary (arbitrary, shrink), Gen) @@ -103,7 +98,7 @@ newtype Range (n :: Nat) (m :: Nat) a = Range } deriving (Eq, Ord, Show) -toRange :: (LTE n x, LTE x m, KnownNat x, Num a) => Proxy x -> Range n m a +toRange :: (n <= x, x <= m, KnownNat x, Num a) => Proxy x -> Range n m a toRange = Range . fromIntegral . natVal instance (Show a, Num a, Within a n m, KnownNat n, KnownNat m) => Bounded (Range n m a) where @@ -115,19 +110,18 @@ instance NFData (Range n m a) where rnf (Range a) = seq a () instance ToJSON a => ToJSON (Range n m a) where toJSON = toJSON . fromRange -instance (Within a n m, FromJSON a) => FromJSON (Range n m a) where - parseJSON v = parseJSON v >>= maybe (msg sing sing) pure . checked +instance forall a n m. (KnownNat n, KnownNat m, Within a n m, FromJSON a) => FromJSON (Range n m a) where + parseJSON v = parseJSON v >>= maybe msg pure . checked where - msg :: Bounds a => SNat n -> SNat m -> Aeson.Parser (Range n m a) - msg sn sm = fail (errorMsg (fromSing sn) (fromSing sm) "") + msg = fail (errorMsg (natVal (Proxy @n)) (natVal (Proxy @m)) "") rangedSchema :: forall n m d v w a b. - (Within a n m, HasRangedSchemaDocModifier d b) => + (KnownNat n, KnownNat m, Within a n m, HasRangedSchemaDocModifier d b) => SchemaP d v w a b -> SchemaP d v w a (Range n m b) rangedSchema sch = - Range <$> untypedRangedSchema (toInteger (demote @n)) (toInteger (demote @m)) sch + Range <$> untypedRangedSchema (toInteger (natVal (Proxy @n))) (toInteger (natVal (Proxy @m))) sch untypedRangedSchema :: forall d v w a b. @@ -178,16 +172,15 @@ instance S.HasSchema d S.Schema => HasRangedSchemaDocModifier d Word32 where ran instance S.HasSchema d S.Schema => HasRangedSchemaDocModifier d Word64 where rangedSchemaDocModifier _ = numRangedSchemaDocModifier -instance (Within a n m, ToSchema a, HasRangedSchemaDocModifier NamedSwaggerDoc a) => ToSchema (Range n m a) where +instance (KnownNat n, KnownNat m, Within a n m, ToSchema a, HasRangedSchemaDocModifier NamedSwaggerDoc a) => ToSchema (Range n m a) where schema = fromRange .= rangedSchema schema -instance (Within a n m, Cql a) => Cql (Range n m a) where +instance forall a n m. (KnownNat n, KnownNat m, Within a n m, Cql a) => Cql (Range n m a) where ctype = retag (ctype :: Tagged a ColumnType) toCql = toCql . fromRange - fromCql c = fromCql c >>= maybe (msg sing sing) pure . checked + fromCql c = fromCql c >>= maybe msg pure . checked where - msg :: Bounds a => SNat n -> SNat m -> Either String (Range n m a) - msg sn sm = Left (errorMsg (fromSing sn) (fromSing sm) "") + msg = Left (errorMsg (natVal (Proxy @n)) (natVal (Proxy @m)) "") instance (KnownNat n, KnownNat m) => ToParamSchema (Range n m Integer) where toParamSchema = rangedNumToParamSchema @@ -241,25 +234,21 @@ instance S.ToSchema a => S.ToSchema (Range n m a) where declareNamedSchema _ = S.declareNamedSchema (Proxy @a) -instance (Within a n m, FromHttpApiData a) => FromHttpApiData (Range n m a) where +instance (KnownNat n, KnownNat m, Within a n m, FromHttpApiData a) => FromHttpApiData (Range n m a) where parseUrlPiece t = do unchecked <- parseUrlPiece t Bifunctor.first T.pack $ checkedEither @_ @n @m unchecked -type LTE (n :: Nat) (m :: Nat) = (SingI n, SingI m, (n <= m) ~ 'True) +type Within a (n :: Nat) (m :: Nat) = (Bounds a, n <= m) -type Within a (n :: Nat) (m :: Nat) = (Bounds a, LTE n m) +mk :: Bounds a => a -> Nat -> Nat -> Maybe (Range n m a) +mk a n m = + if within a (toInteger n) (toInteger m) + then Just (Range a) + else Nothing -mk :: Bounds a => a -> SNat n -> SNat m -> Maybe (Range n m a) -mk a sn sm = - let n = fromSing sn - m = fromSing sm - in if within a (toInteger n) (toInteger m) - then Just (Range a) - else Nothing - -checked :: Within a n m => a -> Maybe (Range n m a) -checked x = mk x sing sing +checked :: forall n m a. (KnownNat n, KnownNat m, Within a n m) => a -> Maybe (Range n m a) +checked x = mk x (natVal (Proxy @n)) (natVal (Proxy @m)) errorMsg :: (Show a, Show b) => a -> b -> ShowS errorMsg n m = @@ -269,20 +258,20 @@ errorMsg n m = . shows m . showString "]" -checkedEitherMsg :: forall a n m. Within a n m => String -> a -> Either String (Range n m a) +checkedEitherMsg :: forall a n m. (KnownNat n, KnownNat m) => Within a n m => String -> a -> Either String (Range n m a) checkedEitherMsg msg x = do - let sn = sing :: SNat n - sm = sing :: SNat m + let sn = natVal (Proxy @n) + sm = natVal (Proxy @m) case mk x sn sm of - Nothing -> Left $ showString msg . showString ": " . errorMsg (fromSing sn) (fromSing sm) $ "" + Nothing -> Left $ showString msg . showString ": " . errorMsg sn sm $ "" Just r -> Right r -checkedEither :: forall a n m. Within a n m => a -> Either String (Range n m a) +checkedEither :: forall a n m. (KnownNat n, KnownNat m) => Within a n m => a -> Either String (Range n m a) checkedEither x = do - let sn = sing :: SNat n - sm = sing :: SNat m + let sn = natVal (Proxy @n) + sm = natVal (Proxy @m) case mk x sn sm of - Nothing -> Left (errorMsg (fromSing sn) (fromSing sm) "") + Nothing -> Left (errorMsg sn sm "") Just r -> Right r rangedChunks :: forall a n. (Within [a] 1 n, KnownNat n) => [a] -> [Range 1 n [a]] @@ -293,34 +282,33 @@ rangedChunks xs = [] -> [] _ -> Range headPart : rangedChunks tailPart -unsafeRange :: (Show a, Within a n m) => a -> Range n m a -unsafeRange x = fromMaybe (msg sing sing) (checked x) +unsafeRange :: forall a n m. (Show a, KnownNat n, KnownNat m, Within a n m) => a -> Range n m a +unsafeRange x = fromMaybe msg (checked x) where - msg :: SNat n -> SNat m -> Range n m a - msg sn sm = + msg = error . shows x . showString " " - . errorMsg (fromSing sn) (fromSing sm) + . errorMsg (natVal (Proxy @n)) (natVal (Proxy @m)) $ "" -rcast :: (LTE n m, (m <= m') ~ 'True, (n >= n') ~ 'True) => Range n m a -> Range n' m' a +rcast :: (n <= m, m <= m', n >= n') => Range n m a -> Range n' m' a rcast (Range a) = Range a rnil :: Monoid a => Range 0 0 a rnil = Range mempty -rcons, (<|) :: LTE n m => a -> Range n m [a] -> Range n (m + 1) [a] +rcons, (<|) :: n <= m => a -> Range n m [a] -> Range n (m + 1) [a] rcons a (Range aa) = Range (a : aa) infixr 5 <| (<|) = rcons -rinc :: (Integral a, LTE n m) => Range n m a -> Range n (m + 1) a +rinc :: (Integral a, n <= m) => Range n m a -> Range n (m + 1) a rinc (Range a) = Range (a + 1) -rappend :: (LTE n m, LTE n' m', Monoid a) => Range n m a -> Range n' m' a -> Range n (m + m') a +rappend :: (n <= m, n' <= m', Monoid a) => Range n m a -> Range n' m' a -> Range n (m + m') a rappend (Range a) (Range b) = Range (a <> b) rsingleton :: a -> Range 1 1 [a] @@ -413,7 +401,7 @@ instance Bounds (AsciiText r) where ----------------------------------------------------------------------------- -instance (Within a n m, Read a) => Read (Range n m a) where +instance (KnownNat n, KnownNat m, Within a n m, Read a) => Read (Range n m a) where readsPrec p s = fromMaybe [] $ foldr f (Just []) (readsPrec p s) where f :: (Within a n m, Read a) => (a, String) -> Maybe [(Range n m a, String)] -> Maybe [(Range n m a, String)] @@ -422,11 +410,10 @@ instance (Within a n m, Read a) => Read (Range n m a) where ----------------------------------------------------------------------------- -instance (Within a n m, FromByteString a) => FromByteString (Range n m a) where - parser = parser >>= maybe (msg sing sing) pure . checked +instance (KnownNat n, KnownNat m, Within a n m, FromByteString a) => FromByteString (Range n m a) where + parser = parser >>= maybe msg pure . checked where - msg :: Bounds a => SNat n -> SNat m -> Atto.Parser (Range n m a) - msg sn sm = fail (errorMsg (fromSing sn) (fromSing sm) "") + msg = fail (errorMsg (natVal (Proxy @n)) (natVal (Proxy @m)) "") instance ToByteString a => ToByteString (Range n m a) where builder = builder . fromRange @@ -442,20 +429,20 @@ instance Arbitrary (Range m n a) => Arbitrary (Ranged m n a) where arbitrary = Ranged . fromRange <$> arbitrary @(Range m n a) instance - (KnownNat n, KnownNat m, LTE n m, Arbitrary a, Show a) => + (KnownNat n, KnownNat m, n <= m, Arbitrary a, Show a) => Arbitrary (Range n m [a]) where arbitrary = genRangeList @n @m @a arbitrary genRangeList :: - forall (n :: Nat) (m :: Nat) (a :: *). - (Show a, KnownNat n, KnownNat m, LTE n m) => + forall (n :: Nat) (m :: Nat) (a :: Type). + (Show a, KnownNat n, KnownNat m, n <= m) => Gen a -> Gen (Range n m [a]) genRangeList = genRange id instance - (KnownNat n, KnownNat m, LTE n m, Arbitrary a, Show a, Ord a) => + (KnownNat n, KnownNat m, n <= m, Arbitrary a, Show a, Ord a) => Arbitrary (Range n m (Set a)) where arbitrary = genRangeSet @n @m @a arbitrary @@ -465,14 +452,14 @@ instance -- However, it will only show up while running tests and might indicate deeper -- problems, so I'd say that's ok. genRangeSet :: - forall (n :: Nat) (m :: Nat) (a :: *). - (Show a, KnownNat n, KnownNat m, LTE n m, Ord a) => + forall (n :: Nat) (m :: Nat) (a :: Type). + (Show a, KnownNat n, KnownNat m, n <= m, Ord a) => Gen a -> Gen (Range n m (Set a)) genRangeSet gc = (Set.fromList . fromRange <$> genRangeList @n @m @a gc) `QC.suchThatMap` checked -instance (KnownNat n, KnownNat m, LTE n m) => Arbitrary (Range n m Text) where +instance (KnownNat n, KnownNat m, n <= m) => Arbitrary (Range n m Text) where arbitrary = genRangeText arbitrary -- FUTUREWORK: the shrinking could be more general (like genRange) and offer more options @@ -480,27 +467,27 @@ instance (KnownNat n, KnownNat m, LTE n m) => Arbitrary (Range n m Text) where genRangeText :: forall (n :: Nat) (m :: Nat). - (KnownNat n, KnownNat m, LTE n m) => + (KnownNat n, KnownNat m, n <= m) => Gen Char -> Gen (Range n m Text) genRangeText = genRange fromString instance - (AsciiChars c, KnownNat n, KnownNat m, LTE n m, Arbitrary (AsciiChar c)) => + (AsciiChars c, KnownNat n, KnownNat m, n <= m, Arbitrary (AsciiChar c)) => Arbitrary (Range n m (AsciiText c)) where arbitrary = genRangeAsciiText (arbitrary @(AsciiChar c)) genRangeAsciiText :: forall (n :: Nat) (m :: Nat) (c :: Type). - (HasCallStack, KnownNat n, KnownNat m, LTE n m, AsciiChars c) => + (HasCallStack, KnownNat n, KnownNat m, n <= m, AsciiChars c) => Gen (AsciiChar c) -> Gen (Range n m (AsciiText c)) genRangeAsciiText = genRange @n @m fromAsciiChars genRange :: - forall (n :: Nat) (m :: Nat) (a :: *) (b :: *). - (Show b, Bounds b, KnownNat n, KnownNat m, LTE n m) => + forall (n :: Nat) (m :: Nat) (a :: Type) (b :: Type). + (Show b, Bounds b, KnownNat n, KnownNat m, n <= m) => ([a] -> b) -> Gen a -> Gen (Range n m b) @@ -513,17 +500,17 @@ genRange pack_ gc = where grange mi ma gelem = (`replicateM` gelem) =<< QC.chooseInt (mi, ma) -instance (KnownNat n, KnownNat m, LTE n m) => Arbitrary (Range n m Integer) where +instance (KnownNat n, KnownNat m, n <= m) => Arbitrary (Range n m Integer) where arbitrary = genIntegral -instance (KnownNat n, KnownNat m, LTE n m) => Arbitrary (Range n m Word) where +instance (KnownNat n, KnownNat m, n <= m) => Arbitrary (Range n m Word) where arbitrary = genIntegral genIntegral :: forall n m i. - (KnownNat n, KnownNat m, LTE n m, Integral i, Show i, Bounds i, Random i) => + (KnownNat n, KnownNat m, n <= m, Integral i, Show i, Bounds i, Random i) => Gen (Range n m i) genIntegral = unsafeRange @i @n @m <$> QC.choose (fromKnownNat (Proxy @n), fromKnownNat (Proxy @m)) -fromKnownNat :: forall (k :: Nat) (i :: *). (Num i, KnownNat k) => Proxy k -> i +fromKnownNat :: forall (k :: Nat) (i :: Type). (Num i, KnownNat k) => Proxy k -> i fromKnownNat p = fromIntegral $ natVal p diff --git a/libs/types-common/types-common.cabal b/libs/types-common/types-common.cabal index 96fb5b5526e..8abae0632ba 100644 --- a/libs/types-common/types-common.cabal +++ b/libs/types-common/types-common.cabal @@ -122,7 +122,6 @@ library , schema-profunctor , scientific >=0.3.4 , servant-server - , singletons >=2.0 , string-conversions , swagger >=0.3 , swagger2 diff --git a/libs/wire-api-federation/default.nix b/libs/wire-api-federation/default.nix index 3c7d80254e0..35d74d4b0c6 100644 --- a/libs/wire-api-federation/default.nix +++ b/libs/wire-api-federation/default.nix @@ -37,6 +37,7 @@ , servant-client-core , servant-server , singletons +, singletons-th , sop-core , streaming-commons , swagger2 @@ -83,6 +84,7 @@ mkDerivation { servant-client-core servant-server singletons + singletons-th sop-core streaming-commons swagger2 @@ -128,6 +130,7 @@ mkDerivation { servant-client-core servant-server singletons + singletons-th sop-core streaming-commons swagger2 diff --git a/libs/wire-api-federation/src/Wire/API/Federation/API.hs b/libs/wire-api-federation/src/Wire/API/Federation/API.hs index 7fc6e981b02..8da8dc66014 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/API.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/API.hs @@ -29,6 +29,7 @@ module Wire.API.Federation.API ) where +import Data.Kind import Data.Proxy import GHC.TypeLits import Imports @@ -43,7 +44,7 @@ import Wire.API.Routes.Named -- Note: this type family being injective means that in most cases there is no need -- to add component annotations when invoking the federator client -type family FedApi (comp :: Component) = (api :: *) | api -> comp +type family FedApi (comp :: Component) = (api :: Type) | api -> comp type instance FedApi 'Galley = GalleyApi diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Endpoint.hs b/libs/wire-api-federation/src/Wire/API/Federation/Endpoint.hs index 8c6367f2499..509e73aa61b 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Endpoint.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Endpoint.hs @@ -21,12 +21,13 @@ module Wire.API.Federation.Endpoint ) where +import Data.Kind import Servant.API import Wire.API.ApplyMods import Wire.API.Federation.Domain import Wire.API.Routes.Named -type FedEndpointWithMods (mods :: [*]) name input output = +type FedEndpointWithMods (mods :: [Type]) name input output = Named name ( ApplyMods diff --git a/libs/wire-api-federation/wire-api-federation.cabal b/libs/wire-api-federation/wire-api-federation.cabal index 81eae46d309..91c78be795f 100644 --- a/libs/wire-api-federation/wire-api-federation.cabal +++ b/libs/wire-api-federation/wire-api-federation.cabal @@ -102,6 +102,7 @@ library , servant-client-core , servant-server , singletons + , singletons-th , sop-core , streaming-commons , swagger2 @@ -214,6 +215,7 @@ test-suite spec , servant-client-core , servant-server , singletons + , singletons-th , sop-core , streaming-commons , swagger2 diff --git a/libs/wire-api/default.nix b/libs/wire-api/default.nix index 8564a5767ac..700d7743c9f 100644 --- a/libs/wire-api/default.nix +++ b/libs/wire-api/default.nix @@ -79,6 +79,8 @@ , servant-swagger , servant-swagger-ui , singletons +, singletons-base +, singletons-th , sop-core , string-conversions , swagger @@ -177,6 +179,8 @@ mkDerivation { servant-swagger servant-swagger-ui singletons + singletons-base + singletons-th sop-core string-conversions swagger diff --git a/libs/wire-api/src/Wire/API/ApplyMods.hs b/libs/wire-api/src/Wire/API/ApplyMods.hs index ad65fdb28e4..70d5dc98112 100644 --- a/libs/wire-api/src/Wire/API/ApplyMods.hs +++ b/libs/wire-api/src/Wire/API/ApplyMods.hs @@ -17,8 +17,9 @@ module Wire.API.ApplyMods where +import Data.Kind import Servant.API -type family ApplyMods (mods :: [*]) api where +type family ApplyMods (mods :: [Type]) api where ApplyMods '[] api = api ApplyMods (x ': xs) api = x :> ApplyMods xs api diff --git a/libs/wire-api/src/Wire/API/Conversation/Action.hs b/libs/wire-api/src/Wire/API/Conversation/Action.hs index 83edcf73e21..5d98193fd37 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Action.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Action.hs @@ -36,6 +36,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A import qualified Data.Aeson.KeyMap as A import Data.Id +import Data.Kind import qualified Data.List.NonEmpty as NonEmptyList import Data.Qualified (Qualified) import Data.Schema hiding (tag) @@ -52,7 +53,7 @@ import Wire.Arbitrary (Arbitrary (..)) -- | We use this type family instead of a sum type to be able to define -- individual effects per conversation action. See 'HasConversationActionEffects'. -type family ConversationAction (tag :: ConversationActionTag) :: * where +type family ConversationAction (tag :: ConversationActionTag) :: Type where ConversationAction 'ConversationJoinTag = ConversationJoin ConversationAction 'ConversationLeaveTag = () ConversationAction 'ConversationMemberUpdateTag = ConversationMemberUpdate diff --git a/libs/wire-api/src/Wire/API/Conversation/Role.hs b/libs/wire-api/src/Wire/API/Conversation/Role.hs index e215b72db88..43ea2f5a7e2 100644 --- a/libs/wire-api/src/Wire/API/Conversation/Role.hs +++ b/libs/wire-api/src/Wire/API/Conversation/Role.hs @@ -85,6 +85,64 @@ import Imports import qualified Test.QuickCheck as QC import Wire.Arbitrary (Arbitrary (arbitrary), GenericUniform (..)) +-------------------------------------------------------------------------------- +-- Action + +newtype Actions = Actions + { allowedActions :: Set Action + } + deriving stock (Eq, Show, Generic) + deriving newtype (Arbitrary) + +allActions :: Actions +allActions = Actions $ Set.fromList [minBound .. maxBound] + +-- | These conversation-level permissions. Analogous to the team-level permissions called +-- 'Perm' (or 'Permissions'). +data Action + = AddConversationMember + | RemoveConversationMember + | ModifyConversationName + | ModifyConversationMessageTimer + | ModifyConversationReceiptMode + | ModifyConversationAccess + | ModifyOtherConversationMember + | LeaveConversation + | DeleteConversation + deriving stock (Eq, Ord, Show, Enum, Bounded, Generic) + deriving (Arbitrary) via (GenericUniform Action) + deriving (S.ToSchema) via (S.CustomSwagger '[S.ConstructorTagModifier S.CamelToSnake] Action) + +type family ActionName (a :: Action) :: Symbol where + ActionName 'AddConversationMember = "add_conversation_member" + ActionName 'RemoveConversationMember = "remove_conversation_member" + ActionName 'ModifyConversationName = "modify_conversation_name" + ActionName 'ModifyConversationMessageTimer = "modify_conversation_message_timer" + ActionName 'ModifyConversationReceiptMode = "modify_conversation_receipt_mode" + ActionName 'ModifyConversationAccess = "modify_conversation_access" + ActionName 'ModifyOtherConversationMember = "modify_other_conversation_member" + ActionName 'LeaveConversation = "leave_conversation" + ActionName 'DeleteConversation = "delete_conversation" + +typeConversationRoleAction :: Doc.DataType +typeConversationRoleAction = + Doc.string $ + Doc.enum + [ "add_conversation_member", + "remove_conversation_member", + "modify_conversation_name", + "modify_conversation_message_timer", + "modify_conversation_receipt_mode", + "modify_conversation_access", + "modify_other_conversation_member", + "leave_conversation", + "delete_conversation" + ] + +A.deriveJSON A.defaultOptions {A.constructorTagModifier = A.camelTo2 '_'} ''Action + +$(genSingletons [''Action]) + -------------------------------------------------------------------------------- -- Role @@ -251,61 +309,3 @@ isValidRoleName = *> count 126 (optional (satisfy chars)) *> endOfInput chars = inClass "a-z0-9_" - --------------------------------------------------------------------------------- --- Action - -newtype Actions = Actions - { allowedActions :: Set Action - } - deriving stock (Eq, Show, Generic) - deriving newtype (Arbitrary) - -allActions :: Actions -allActions = Actions $ Set.fromList [minBound .. maxBound] - --- | These conversation-level permissions. Analogous to the team-level permissions called --- 'Perm' (or 'Permissions'). -data Action - = AddConversationMember - | RemoveConversationMember - | ModifyConversationName - | ModifyConversationMessageTimer - | ModifyConversationReceiptMode - | ModifyConversationAccess - | ModifyOtherConversationMember - | LeaveConversation - | DeleteConversation - deriving stock (Eq, Ord, Show, Enum, Bounded, Generic) - deriving (Arbitrary) via (GenericUniform Action) - deriving (S.ToSchema) via (S.CustomSwagger '[S.ConstructorTagModifier S.CamelToSnake] Action) - -type family ActionName (a :: Action) :: Symbol where - ActionName 'AddConversationMember = "add_conversation_member" - ActionName 'RemoveConversationMember = "remove_conversation_member" - ActionName 'ModifyConversationName = "modify_conversation_name" - ActionName 'ModifyConversationMessageTimer = "modify_conversation_message_timer" - ActionName 'ModifyConversationReceiptMode = "modify_conversation_receipt_mode" - ActionName 'ModifyConversationAccess = "modify_conversation_access" - ActionName 'ModifyOtherConversationMember = "modify_other_conversation_member" - ActionName 'LeaveConversation = "leave_conversation" - ActionName 'DeleteConversation = "delete_conversation" - -typeConversationRoleAction :: Doc.DataType -typeConversationRoleAction = - Doc.string $ - Doc.enum - [ "add_conversation_member", - "remove_conversation_member", - "modify_conversation_name", - "modify_conversation_message_timer", - "modify_conversation_receipt_mode", - "modify_conversation_access", - "modify_other_conversation_member", - "leave_conversation", - "delete_conversation" - ] - -A.deriveJSON A.defaultOptions {A.constructorTagModifier = A.camelTo2 '_'} ''Action - -$(genSingletons [''Action]) diff --git a/libs/wire-api/src/Wire/API/Error.hs b/libs/wire-api/src/Wire/API/Error.hs index c34bf13a5f9..92c6c384275 100644 --- a/libs/wire-api/src/Wire/API/Error.hs +++ b/libs/wire-api/src/Wire/API/Error.hs @@ -46,6 +46,7 @@ where import Control.Lens (at, (%~), (.~), (?~)) import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as A +import Data.Kind import Data.Metrics.Servant import Data.Proxy import Data.SOP @@ -57,7 +58,6 @@ import GHC.TypeLits import Imports hiding (All) import Network.HTTP.Types.Status import qualified Network.Wai.Utilities.Error as Wai -import Numeric.Natural import Polysemy import Polysemy.Error import Servant @@ -204,8 +204,12 @@ errorResponseSwagger = addStaticErrorToSwagger :: forall e. KnownError e => S.Swagger -> S.Swagger addStaticErrorToSwagger = - S.allOperations . S.responses . S.responses . at (fromIntegral (eCode err)) - %~ Just . addRef + S.allOperations + . S.responses + . S.responses + . at (fromIntegral (eCode err)) + %~ Just + . addRef where err = dynError @e resp = errorResponseSwagger @e @@ -267,7 +271,7 @@ instance KnownError e => APIError (SStaticError e) where -------------------------------------------------------------------------------- -- MultiVerb support -type family RespondWithStaticError (s :: StaticError) :: * where +type family RespondWithStaticError (s :: StaticError) :: Type where RespondWithStaticError ('StaticError s l m) = RespondAs JSON s m DynError type family StaticErrorStatus (s :: StaticError) :: Nat where diff --git a/libs/wire-api/src/Wire/API/Error/Galley.hs b/libs/wire-api/src/Wire/API/Error/Galley.hs index 65596d70fac..782c02e0238 100644 --- a/libs/wire-api/src/Wire/API/Error/Galley.hs +++ b/libs/wire-api/src/Wire/API/Error/Galley.hs @@ -31,8 +31,7 @@ where import Control.Lens ((%~)) import Data.Aeson (FromJSON (..), ToJSON (..)) -import Data.Singletons.CustomStar (genSingletons) -import Data.Singletons.Prelude (Show_) +import Data.Singletons.TH (genSingletons) import qualified Data.Swagger as S import Data.Tagged import GHC.TypeLits @@ -40,6 +39,7 @@ import Imports import qualified Network.Wai.Utilities.Error as Wai import Polysemy import Polysemy.Error +import Prelude.Singletons (Show_) import Wire.API.Conversation.Role import Wire.API.Error import qualified Wire.API.Error.Brig as BrigError diff --git a/libs/wire-api/src/Wire/API/MLS/Extension.hs b/libs/wire-api/src/Wire/API/MLS/Extension.hs index 406adfa7e8a..5093398adf9 100644 --- a/libs/wire-api/src/Wire/API/MLS/Extension.hs +++ b/libs/wire-api/src/Wire/API/MLS/Extension.hs @@ -44,6 +44,7 @@ module Wire.API.MLS.Extension where import Data.Binary +import Data.Kind import Data.Singletons.TH import Data.Time.Clock.POSIX import Imports @@ -86,7 +87,7 @@ data ExtensionTag $(genSingletons [''ExtensionTag]) -type family ExtensionType (t :: ExtensionTag) :: * where +type family ExtensionType (t :: ExtensionTag) :: Type where ExtensionType 'CapabilitiesExtensionTag = Capabilities ExtensionType 'LifetimeExtensionTag = Lifetime diff --git a/libs/wire-api/src/Wire/API/MLS/Message.hs b/libs/wire-api/src/Wire/API/MLS/Message.hs index 2393aa06e2a..c70f736bfbe 100644 --- a/libs/wire-api/src/Wire/API/MLS/Message.hs +++ b/libs/wire-api/src/Wire/API/MLS/Message.hs @@ -49,8 +49,8 @@ import Data.Binary import Data.Binary.Get import Data.Binary.Put import qualified Data.ByteArray as BA --- import qualified Data.ByteString as BS import Data.Json.Util +import Data.Kind import Data.Schema import Data.Singletons.TH import qualified Data.Swagger as S @@ -74,7 +74,7 @@ $(genSingletons [''WireFormatTag]) instance ParseMLS WireFormatTag where parseMLS = parseMLSEnum @Word8 "wire format" -data family MessageExtraFields (tag :: WireFormatTag) :: * +data family MessageExtraFields (tag :: WireFormatTag) :: Type data instance MessageExtraFields 'MLSPlainText = MessageExtraFields { msgSignature :: ByteString, @@ -226,7 +226,7 @@ instance ParseMLS SomeMessage where MLSPlainText -> SomeMessage SMLSPlainText <$> parseMLS MLSCipherText -> SomeMessage SMLSCipherText <$> parseMLS -data family Sender (tag :: WireFormatTag) :: * +data family Sender (tag :: WireFormatTag) :: Type data instance Sender 'MLSCipherText = EncryptedSender {esData :: ByteString} deriving (Eq, Show) @@ -268,7 +268,7 @@ instance SerialiseMLS (Sender 'MLSPlainText) where put x serialiseMLS NewMemberSender = serialiseMLS NewMemberSenderTag -data family MessagePayload (tag :: WireFormatTag) :: * +data family MessagePayload (tag :: WireFormatTag) :: Type deriving instance Eq (MessagePayload 'MLSPlainText) diff --git a/libs/wire-api/src/Wire/API/MLS/Serialisation.hs b/libs/wire-api/src/Wire/API/MLS/Serialisation.hs index 3e99b33cfb0..0881c317739 100644 --- a/libs/wire-api/src/Wire/API/MLS/Serialisation.hs +++ b/libs/wire-api/src/Wire/API/MLS/Serialisation.hs @@ -58,6 +58,7 @@ import Data.Binary.Put import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import Data.Json.Util +import Data.Kind import Data.Proxy import Data.Schema import qualified Data.Swagger as S @@ -125,7 +126,7 @@ serialiseMLSOptional p (Just x) = do -- corresponding enumeration index. This makes it possible to parse enumeration -- types that don't contain an explicit constructor for a "reserved" value. parseMLSEnum :: - forall (w :: *) a. + forall (w :: Type) a. (Bounded a, Enum a, Integral w, Binary w) => String -> Get a diff --git a/libs/wire-api/src/Wire/API/MakesFederatedCall.hs b/libs/wire-api/src/Wire/API/MakesFederatedCall.hs index a6abb32dc02..78de9518415 100644 --- a/libs/wire-api/src/Wire/API/MakesFederatedCall.hs +++ b/libs/wire-api/src/Wire/API/MakesFederatedCall.hs @@ -27,6 +27,7 @@ where import Data.Aeson (Value (..)) import Data.Constraint +import Data.Kind import Data.Metrics.Servant import Data.Proxy import Data.Swagger.Operation (addExtensions) @@ -72,7 +73,7 @@ synthesizeCallsFed = unsafeCoerce $ Dict @Nullary -- constraints on handlers. data MakesFederatedCall (comp :: Component) (name :: Symbol) -instance (HasServer api ctx) => HasServer (MakesFederatedCall comp name :> api :: *) ctx where +instance (HasServer api ctx) => HasServer (MakesFederatedCall comp name :> api :: Type) ctx where -- \| This should have type @CallsFed comp name => ServerT api m@, but GHC -- complains loudly thinking this is a polytype. We need to introduce the -- 'CallsFed' constraint so that we can eliminate it via @@ -82,11 +83,11 @@ instance (HasServer api ctx) => HasServer (MakesFederatedCall comp name :> api : route _ ctx f = route (Proxy @api) ctx $ fmap ($ synthesizeCallsFed @comp @name) f hoistServerWithContext _ ctx f s = hoistServerWithContext (Proxy @api) ctx f . s -instance HasLink api => HasLink (MakesFederatedCall comp name :> api :: *) where +instance HasLink api => HasLink (MakesFederatedCall comp name :> api :: Type) where type MkLink (MakesFederatedCall comp name :> api) x = MkLink api x toLink f _ l = toLink f (Proxy @api) l -instance RoutesToPaths api => RoutesToPaths (MakesFederatedCall comp name :> api :: *) where +instance RoutesToPaths api => RoutesToPaths (MakesFederatedCall comp name :> api :: Type) where getRoutes = getRoutes @api -- | Get a symbol representation of our component. @@ -97,7 +98,7 @@ type family ShowComponent (x :: Component) :: Symbol where -- | 'MakesFederatedCall' annotates the swagger documentation with an extension -- tag @x-wire-makes-federated-calls-to@. -instance (HasSwagger api, KnownSymbol name, KnownSymbol (ShowComponent comp)) => HasSwagger (MakesFederatedCall comp name :> api :: *) where +instance (HasSwagger api, KnownSymbol name, KnownSymbol (ShowComponent comp)) => HasSwagger (MakesFederatedCall comp name :> api :: Type) where toSwagger _ = toSwagger (Proxy @api) & addExtensions @@ -116,7 +117,7 @@ mergeJSONArray :: Value -> Value -> Value mergeJSONArray (Array x) (Array y) = Array $ x <> y mergeJSONArray _ _ = error "impossible! bug in construction of federated calls JSON" -instance HasClient m api => HasClient m (MakesFederatedCall comp name :> api :: *) where +instance HasClient m api => HasClient m (MakesFederatedCall comp name :> api :: Type) where type Client m (MakesFederatedCall comp name :> api) = Client m api clientWithRoute p _ = clientWithRoute p $ Proxy @api hoistClientMonad p _ f c = hoistClientMonad p (Proxy @api) f c diff --git a/libs/wire-api/src/Wire/API/Provider/Service/Tag.hs b/libs/wire-api/src/Wire/API/Provider/Service/Tag.hs index 60be42a4273..555c5fa31ae 100644 --- a/libs/wire-api/src/Wire/API/Provider/Service/Tag.hs +++ b/libs/wire-api/src/Wire/API/Provider/Service/Tag.hs @@ -44,10 +44,11 @@ import qualified Data.Aeson as JSON import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Char8 as C8 import Data.ByteString.Conversion -import Data.Range (LTE, Range, fromRange) +import Data.Range (Range, fromRange) import qualified Data.Range as Range import qualified Data.Set as Set import qualified Data.Text.Encoding as Text +import Data.Type.Ord import GHC.TypeLits (KnownNat, Nat) import Imports import Wire.Arbitrary (Arbitrary (..), GenericUniform (..)) @@ -180,10 +181,10 @@ newtype QueryAnyTags (m :: Nat) (n :: Nat) = QueryAnyTags {queryAnyTagsRange :: Range m n (Set (QueryAllTags m n))} deriving stock (Eq, Show, Ord) -instance (KnownNat m, KnownNat n, LTE m n) => Arbitrary (QueryAnyTags m n) where +instance (KnownNat m, KnownNat n, m <= n) => Arbitrary (QueryAnyTags m n) where arbitrary = QueryAnyTags <$> arbitrary -queryAnyTags :: LTE m n => MatchAny -> Maybe (QueryAnyTags m n) +queryAnyTags :: (KnownNat m, KnownNat n, m <= n) => MatchAny -> Maybe (QueryAnyTags m n) queryAnyTags t = do x <- mapM queryAllTags (Set.toList (matchAnySet t)) QueryAnyTags <$> Range.checked (Set.fromList x) @@ -199,7 +200,7 @@ instance ToByteString (QueryAnyTags m n) where . queryAnyTagsRange -- | QueryAny ::= QueryAll { "," QueryAll } -instance LTE m n => FromByteString (QueryAnyTags m n) where +instance (KnownNat n, KnownNat m, m <= n) => FromByteString (QueryAnyTags m n) where parser = do bs <- C8.split ',' <$> parser ts <- mapM (either fail pure . runParser parser) bs @@ -211,10 +212,10 @@ newtype QueryAllTags (m :: Nat) (n :: Nat) = QueryAllTags {queryAllTagsRange :: Range m n (Set ServiceTag)} deriving stock (Eq, Show, Ord) -instance (KnownNat m, KnownNat n, LTE m n) => Arbitrary (QueryAllTags m n) where +instance (KnownNat m, KnownNat n, m <= n) => Arbitrary (QueryAllTags m n) where arbitrary = QueryAllTags <$> arbitrary -queryAllTags :: LTE m n => MatchAll -> Maybe (QueryAllTags m n) +queryAllTags :: (KnownNat m, KnownNat n, m <= n) => MatchAll -> Maybe (QueryAllTags m n) queryAllTags = fmap QueryAllTags . Range.checked . matchAllSet -- | QueryAll ::= tag { "." tag } @@ -228,7 +229,7 @@ instance ToByteString (QueryAllTags m n) where . queryAllTagsRange -- | QueryAll ::= tag { "." tag } -instance LTE m n => FromByteString (QueryAllTags m n) where +instance (KnownNat m, KnownNat n, m <= n) => FromByteString (QueryAllTags m n) where parser = do bs <- C8.split '.' <$> parser ts <- mapM (either fail pure . runParser parser) bs diff --git a/libs/wire-api/src/Wire/API/Routes/Cookies.hs b/libs/wire-api/src/Wire/API/Routes/Cookies.hs index af51f950b66..3feadafb109 100644 --- a/libs/wire-api/src/Wire/API/Routes/Cookies.hs +++ b/libs/wire-api/src/Wire/API/Routes/Cookies.hs @@ -17,6 +17,7 @@ module Wire.API.Routes.Cookies where +import Data.Kind import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Map as M import Data.Metrics.Servant @@ -42,12 +43,12 @@ data (:::) a b -- @@ -- results in a cookie with name "foo" containing a 64-bit integer, and a -- cookie with name "bar" containing an arbitrary text value. -data Cookies (cs :: [*]) +data Cookies (cs :: [Type]) type CookieHeader cs = Header "Cookie" (CookieTuple cs) -- CookieTypes = map snd -type family CookieTypes (cs :: [*]) :: [*] +type family CookieTypes (cs :: [Type]) :: [Type] type instance CookieTypes '[] = '[] @@ -60,9 +61,9 @@ type CookieMap = Map ByteString (NonEmpty ByteString) instance HasSwagger api => HasSwagger (Cookies cs :> api) where toSwagger _ = toSwagger (Proxy @api) -class CookieArgs (cs :: [*]) where +class CookieArgs (cs :: [Type]) where -- example: AddArgs ["foo" :: Foo, "bar" :: Bar] a = Foo -> Bar -> a - type AddArgs cs a :: * + type AddArgs cs a :: Type uncurryArgs :: AddArgs cs a -> CookieTuple cs -> a mapArgs :: (a -> b) -> AddArgs cs a -> AddArgs cs b @@ -81,7 +82,7 @@ instance KnownSymbol label, FromHttpApiData x ) => - CookieArgs ((label ::: (x :: *)) ': cs) + CookieArgs ((label ::: (x :: Type)) ': cs) where type AddArgs ((label ::: x) ': cs) a = [Either Text x] -> AddArgs cs a uncurryArgs f (CookieTuple (I x :* xs)) = uncurryArgs @cs (f x) (CookieTuple xs) diff --git a/libs/wire-api/src/Wire/API/Routes/MultiTablePaging.hs b/libs/wire-api/src/Wire/API/Routes/MultiTablePaging.hs index 4a8f7379677..f0b697e059d 100644 --- a/libs/wire-api/src/Wire/API/Routes/MultiTablePaging.hs +++ b/libs/wire-api/src/Wire/API/Routes/MultiTablePaging.hs @@ -62,7 +62,7 @@ data GetMultiTablePageRequest (name :: Symbol) (tables :: Type) (max :: Nat) (de -- 24 | deriving ToJSON via Schema (GetMultiTablePageRequest name tables max def) -- | ^^^^^^ -type RequestSchemaConstraint name tables max def = (KnownNat max, KnownNat def, Within Int32 1 max, LTE 1 def, LTE def max, PagingTable tables, KnownSymbol name) +type RequestSchemaConstraint name tables max def = (KnownNat max, KnownNat def, Within Int32 1 max, 1 <= def, def <= max, PagingTable tables, KnownSymbol name) deriving via Schema (GetMultiTablePageRequest name tables max def) diff --git a/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs b/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs index e8d79bee601..6cba238dc35 100644 --- a/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs +++ b/libs/wire-api/src/Wire/API/Routes/MultiVerb.hs @@ -58,6 +58,7 @@ import Data.Containers.ListUtils import Data.Either.Combinators (leftToMaybe) import Data.HashMap.Strict.InsOrd (InsOrdHashMap) import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap +import Data.Kind import Data.Metrics.Servant import Data.Proxy import Data.SOP @@ -94,13 +95,13 @@ type Declare = S.Declare (S.Definitions S.Schema) -- Includes status code, description, and return type. The content type of the -- response is determined dynamically using the accept header and the list of -- supported content types specified in the containing 'MultiVerb' type. -data Respond (s :: Nat) (desc :: Symbol) (a :: *) +data Respond (s :: Nat) (desc :: Symbol) (a :: Type) -- | A type to describe a 'MultiVerb' response with a fixed content type. -- -- Similar to 'Respond', but hardcodes the content type to be used for -- generating the response. -data RespondAs ct (s :: Nat) (desc :: Symbol) (a :: *) +data RespondAs ct (s :: Nat) (desc :: Symbol) (a :: Type) -- | A type to describe a 'MultiVerb' response with an empty body. -- @@ -111,7 +112,7 @@ type RespondEmpty s desc = RespondAs '() s desc () -- -- Includes status code, description, framing strategy and content type. Note -- that the handler return type is hardcoded to be 'SourceIO ByteString'. -data RespondStreaming (s :: Nat) (desc :: Symbol) (framing :: *) (ct :: *) +data RespondStreaming (s :: Nat) (desc :: Symbol) (framing :: Type) (ct :: Type) -- | The result of parsing a response as a union alternative of type 'a'. -- @@ -151,11 +152,11 @@ instance MonadPlus UnrenderResult where class IsSwaggerResponse a where responseSwagger :: Declare S.Response -type family ResponseType a :: * +type family ResponseType a :: Type class IsWaiBody (ResponseBody a) => IsResponse cs a where type ResponseStatus a :: Nat - type ResponseBody a :: * + type ResponseBody a :: Type responseRender :: AcceptHeader -> ResponseType a -> Maybe (ResponseF (ResponseBody a)) responseUnrender :: M.MediaType -> ResponseF (ResponseBody a) -> UnrenderResult (ResponseType a) @@ -211,7 +212,7 @@ instance MimeRender ct a, MimeUnrender ct a ) => - IsResponse cs (RespondAs (ct :: *) s desc a) + IsResponse cs (RespondAs (ct :: Type) s desc a) where type ResponseStatus (RespondAs ct s desc a) = s type ResponseBody (RespondAs ct s desc a) = LByteString @@ -248,7 +249,7 @@ instance KnownStatus s => IsResponse cs (RespondAs '() s desc ()) where instance (KnownSymbol desc, S.ToSchema a) => - IsSwaggerResponse (RespondAs (ct :: *) s desc a) + IsSwaggerResponse (RespondAs (ct :: Type) s desc a) where responseSwagger = simpleResponseSwagger @a @desc @@ -294,7 +295,7 @@ instance (KnownStatus s, KnownSymbol desc) => IsSwaggerResponse (RespondStreamin -- * @hs@: type-level list of headers -- * @a@: return type (with headers) -- * @r@: underlying response (without headers) -data WithHeaders (hs :: [*]) (a :: *) (r :: *) +data WithHeaders (hs :: [Type]) (a :: Type) (r :: Type) -- | This is used to convert a response containing headers to a custom type -- including the information in the headers. @@ -312,7 +313,7 @@ instance AsHeaders '[h] a (a, h) where toHeaders (t, cc) = (I cc :* Nil, t) fromHeaders (I cc :* Nil, t) = (t, cc) -data DescHeader (name :: Symbol) (desc :: Symbol) (a :: *) +data DescHeader (name :: Symbol) (desc :: Symbol) (a :: Type) -- | A wrapper to turn a response header into an optional one. data OptHeader h @@ -425,7 +426,7 @@ instance class IsSwaggerResponseList as where responseListSwagger :: Declare (InsOrdHashMap S.HttpStatusCode S.Response) -type family ResponseTypes (as :: [*]) where +type family ResponseTypes (as :: [Type]) where ResponseTypes '[] = '[] ResponseTypes (a ': as) = ResponseType a ': ResponseTypes as @@ -501,7 +502,7 @@ combineSwaggerSchema s1 s2 -- instance. -- * Headers can be attached to individual responses, also without affecting -- the handler return type. -data MultiVerb (method :: StdMethod) cs (as :: [*]) (r :: *) +data MultiVerb (method :: StdMethod) cs (as :: [Type]) (r :: Type) -- | A 'MultiVerb' endpoint with a single response. type MultiVerb1 m cs a = MultiVerb m cs '[a] (ResponseType a) @@ -512,7 +513,7 @@ type MultiVerb1 m cs a = MultiVerb m cs '[a] (ResponseType a) -- Any glue code necessary to convert application types to and from the -- canonical 'Union' type corresponding to a 'MultiVerb' endpoint should be -- packaged into an 'AsUnion' instance. -class AsUnion (as :: [*]) (r :: *) where +class AsUnion (as :: [Type]) (r :: Type) where toUnion :: r -> Union (ResponseTypes as) fromUnion :: Union (ResponseTypes as) -> r @@ -622,7 +623,7 @@ instance AsConstructor '[a] (Respond code desc a) where toConstructor x = I x :* Nil fromConstructor = unI . hd -instance AsConstructor '[a] (RespondAs (ct :: *) code desc a) where +instance AsConstructor '[a] (RespondAs (ct :: Type) code desc a) where toConstructor x = I x :* Nil fromConstructor = unI . hd @@ -718,7 +719,7 @@ instance instance (SwaggerMethod method, IsSwaggerResponseList as, AllMime cs) => - S.HasSwagger (MultiVerb method (cs :: [*]) as r) + S.HasSwagger (MultiVerb method (cs :: [Type]) as r) where toSwagger _ = mempty diff --git a/libs/wire-api/src/Wire/API/Routes/Named.hs b/libs/wire-api/src/Wire/API/Routes/Named.hs index df29b1a53bf..fd5c3f53137 100644 --- a/libs/wire-api/src/Wire/API/Routes/Named.hs +++ b/libs/wire-api/src/Wire/API/Routes/Named.hs @@ -17,6 +17,7 @@ module Wire.API.Routes.Named where +import Data.Kind import Data.Metrics.Servant import Data.Proxy import GHC.TypeLits @@ -51,7 +52,7 @@ instance HasClient m api => HasClient m (Named n api) where clientWithRoute pm _ req = clientWithRoute pm (Proxy @api) req hoistClientMonad pm _ f = hoistClientMonad pm (Proxy @api) f -type family FindName n (api :: *) :: (n, *) where +type family FindName n (api :: Type) :: (n, Type) where FindName n (Named name api) = '(name, api) FindName n (x :> api) = AddPrefix x (FindName n api) FindName n api = '(TypeError ('Text "Named combinator not found"), api) @@ -89,7 +90,7 @@ type family FMap (f :: a -> b) (m :: Maybe a) :: Maybe b where FMap _ 'Nothing = 'Nothing FMap f ('Just a) = 'Just (f a) -type family LookupEndpoint api name :: Maybe (*) where +type family LookupEndpoint api name :: Maybe Type where LookupEndpoint (Named name endpoint) name = 'Just endpoint LookupEndpoint (api1 :<|> api2) name = MappendMaybe diff --git a/libs/wire-api/src/Wire/API/Routes/Public.hs b/libs/wire-api/src/Wire/API/Routes/Public.hs index deff0d727c5..cdd0b835baa 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public.hs @@ -40,6 +40,7 @@ import Control.Lens ((<>~)) import Data.Domain import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap import Data.Id as Id +import Data.Kind import Data.Metrics.Servant import Data.Qualified import Data.Swagger @@ -87,8 +88,8 @@ class IsZType (ztype :: ZType) ctx where type ZHeader ztype :: Symbol - type ZParam ztype :: * - type ZQualifiedParam ztype :: * + type ZParam ztype :: Type + type ZQualifiedParam ztype :: Type qualifyZParam :: Context ctx -> ZParam ztype -> ZQualifiedParam ztype @@ -154,7 +155,7 @@ instance IsZType 'ZAuthProvider ctx where instance HasTokenType 'ZAuthProvider where tokenType = Just "provider" -data ZAuthServant (ztype :: ZType) (opts :: [*]) +data ZAuthServant (ztype :: ZType) (opts :: [Type]) type InternalAuthDefOpts = '[Servant.Required, Servant.Strict] diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs b/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs index f31683711fd..b4ea06f2bba 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Cargohold.hs @@ -18,6 +18,7 @@ module Wire.API.Routes.Public.Cargohold where import Data.Id +import Data.Kind import Data.Metrics.Servant import Data.Qualified import Data.SOP @@ -40,7 +41,7 @@ import Wire.API.Routes.Version data PrincipalTag = UserPrincipalTag | BotPrincipalTag | ProviderPrincipalTag deriving (Eq, Show) -type family PrincipalId (tag :: PrincipalTag) = (id :: *) | id -> tag where +type family PrincipalId (tag :: PrincipalTag) = (id :: Type) | id -> tag where PrincipalId 'UserPrincipalTag = Local UserId PrincipalId 'BotPrincipalTag = BotId PrincipalId 'ProviderPrincipalTag = ProviderId diff --git a/libs/wire-api/src/Wire/API/Routes/QualifiedCapture.hs b/libs/wire-api/src/Wire/API/Routes/QualifiedCapture.hs index 6356821b5d3..ffbea771720 100644 --- a/libs/wire-api/src/Wire/API/Routes/QualifiedCapture.hs +++ b/libs/wire-api/src/Wire/API/Routes/QualifiedCapture.hs @@ -22,6 +22,7 @@ module Wire.API.Routes.QualifiedCapture where import Data.Domain +import Data.Kind import Data.Metrics.Servant import Data.Qualified import Data.Swagger @@ -35,7 +36,7 @@ import Servant.Server.Internal.ErrorFormatter import Servant.Swagger -- | Capture a value qualified by a domain, with modifiers. -data QualifiedCapture' (mods :: [*]) (capture :: Symbol) (a :: *) +data QualifiedCapture' (mods :: [Type]) (capture :: Symbol) (a :: Type) -- | Capture a value qualified by a domain. -- diff --git a/libs/wire-api/src/Wire/API/Routes/Versioned.hs b/libs/wire-api/src/Wire/API/Routes/Versioned.hs index bb9dcf766ad..ead87a48dc6 100644 --- a/libs/wire-api/src/Wire/API/Routes/Versioned.hs +++ b/libs/wire-api/src/Wire/API/Routes/Versioned.hs @@ -18,6 +18,7 @@ module Wire.API.Routes.Versioned where import Data.Aeson (FromJSON, ToJSON) +import Data.Kind import Data.Metrics.Servant import Data.Schema import Data.Singletons @@ -34,7 +35,7 @@ import Wire.API.Routes.Version -------------------------------------- -- Versioned requests -data VersionedReqBody' v (mods :: [*]) (ct :: [*]) (a :: *) +data VersionedReqBody' v (mods :: [Type]) (ct :: [Type]) (a :: Type) type VersionedReqBody v = VersionedReqBody' v '[Required, Strict] @@ -69,7 +70,7 @@ instance -------------------------------------------------------------------------------- -- Versioned responses -data VersionedRespond v (s :: Nat) (desc :: Symbol) (a :: *) +data VersionedRespond v (s :: Nat) (desc :: Symbol) (a :: Type) type instance ResponseType (VersionedRespond v s desc a) = a diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index a7d74ad0341..c63f37c51c7 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -96,6 +96,7 @@ import qualified Data.ByteString.UTF8 as UTF8 import Data.Domain (Domain) import Data.Either.Extra (maybeToEither) import Data.Id +import Data.Kind import Data.Proxy import Data.Schema import Data.Scientific (toBoundedInteger) @@ -184,7 +185,7 @@ featureNameBS = UTF8.fromString $ symbolVal (Proxy @(FeatureSymbol cfg)) ---------------------------------------------------------------------- -- WithStatusBase -data WithStatusBase (m :: * -> *) (cfg :: *) = WithStatusBase +data WithStatusBase (m :: Type -> Type) (cfg :: Type) = WithStatusBase { wsbStatus :: m FeatureStatus, wsbLockStatus :: m LockStatus, wsbConfig :: m cfg, @@ -223,7 +224,7 @@ setConfig c (WithStatusBase s ls _ ttl) = WithStatusBase s ls (Identity c) ttl setWsTTL :: FeatureTTL -> WithStatus cfg -> WithStatus cfg setWsTTL ttl (WithStatusBase s ls c _) = WithStatusBase s ls c (Identity ttl) -type WithStatus (cfg :: *) = WithStatusBase Identity cfg +type WithStatus (cfg :: Type) = WithStatusBase Identity cfg deriving instance (Eq cfg) => Eq (WithStatus cfg) @@ -267,7 +268,7 @@ withStatusModel = ---------------------------------------------------------------------- -- WithStatusPatch -type WithStatusPatch (cfg :: *) = WithStatusBase Maybe cfg +type WithStatusPatch (cfg :: Type) = WithStatusBase Maybe cfg deriving instance (Eq cfg) => Eq (WithStatusPatch cfg) @@ -325,7 +326,7 @@ instance (Arbitrary cfg, IsFeatureConfig cfg) => Arbitrary (WithStatusPatch cfg) -- if we switch to `unlocked`, we auto-enable the feature, and if we switch to locked, we -- auto-disable it. But we need to change the API to force clients to use `lockStatus` -- instead of `status`, current behavior is just wrong. -data WithStatusNoLock (cfg :: *) = WithStatusNoLock +data WithStatusNoLock (cfg :: Type) = WithStatusNoLock { wssStatus :: FeatureStatus, wssConfig :: cfg, wssTTL :: FeatureTTL @@ -543,7 +544,7 @@ instance ToSchema LockStatusResponse where LockStatusResponse <$> _unlockStatus .= field "lockStatus" schema -newtype ImplicitLockStatus (cfg :: *) = ImplicitLockStatus {_unImplicitLockStatus :: WithStatus cfg} +newtype ImplicitLockStatus (cfg :: Type) = ImplicitLockStatus {_unImplicitLockStatus :: WithStatus cfg} deriving newtype (Eq, Show, Arbitrary) instance (IsFeatureConfig a, ToSchema a) => ToJSON (ImplicitLockStatus a) where diff --git a/libs/wire-api/src/Wire/API/Team/Member.hs b/libs/wire-api/src/Wire/API/Team/Member.hs index e573a0658ef..2fc27f12d5e 100644 --- a/libs/wire-api/src/Wire/API/Team/Member.hs +++ b/libs/wire-api/src/Wire/API/Team/Member.hs @@ -71,6 +71,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..), Value (..)) import qualified Data.ByteString.Lazy as LBS import Data.Id (UserId) import Data.Json.Util +import Data.Kind import Data.LegalHold (UserLegalHoldStatus (..), defUserLegalHoldStatus) import Data.Misc (PlainTextPassword (..)) import Data.Proxy @@ -86,7 +87,7 @@ import Wire.Arbitrary (Arbitrary, GenericUniform (..)) data PermissionTag = Required | Optional -type family PermissionType (tag :: PermissionTag) = (t :: *) | t -> tag where +type family PermissionType (tag :: PermissionTag) = (t :: Type) | t -> tag where PermissionType 'Required = Permissions PermissionType 'Optional = Maybe Permissions diff --git a/libs/wire-api/src/Wire/API/Team/Permission.hs b/libs/wire-api/src/Wire/API/Team/Permission.hs index 4b5c7802351..98f983b1b3c 100644 --- a/libs/wire-api/src/Wire/API/Team/Permission.hs +++ b/libs/wire-api/src/Wire/API/Team/Permission.hs @@ -53,7 +53,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Bits (testBit, (.|.)) import Data.Schema import qualified Data.Set as Set -import Data.Singletons.TH +import Data.Singletons.Base.TH import qualified Data.Swagger as S import qualified Data.Swagger.Build.Api as Doc import Imports @@ -211,4 +211,5 @@ instance Cql.Cql Permissions where fromCql _ = Left "permissions: udt expected" $(genSingletons [''Perm]) + $(promoteShowInstances [''Perm]) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 08af13b1156..1e41a89c9db 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -150,7 +150,7 @@ import qualified Data.Text.Encoding as T import Data.UUID (UUID, nil) import qualified Data.UUID as UUID import Deriving.Swagger -import GHC.TypeLits (KnownNat, Nat) +import GHC.TypeLits import qualified Generics.SOP as GSOP import Imports import qualified SAML2.WebSSO as SAML @@ -226,14 +226,14 @@ newtype LimitedQualifiedUserIdList (max :: Nat) = LimitedQualifiedUserIdList deriving stock (Eq, Show, Generic) deriving (S.ToSchema) via CustomSwagger '[FieldLabelModifier CamelToSnake] (LimitedQualifiedUserIdList max) -instance (KnownNat max, LTE 1 max) => Arbitrary (LimitedQualifiedUserIdList max) where +instance (KnownNat max, 1 <= max) => Arbitrary (LimitedQualifiedUserIdList max) where arbitrary = LimitedQualifiedUserIdList <$> arbitrary -instance LTE 1 max => FromJSON (LimitedQualifiedUserIdList max) where +instance (KnownNat max, 1 <= max) => FromJSON (LimitedQualifiedUserIdList max) where parseJSON = A.withObject "LimitedQualifiedUserIdList" $ \o -> LimitedQualifiedUserIdList <$> o A..: "qualified_users" -instance LTE 1 max => ToJSON (LimitedQualifiedUserIdList max) where +instance 1 <= max => ToJSON (LimitedQualifiedUserIdList max) where toJSON e = A.object ["qualified_users" A..= qualifiedUsers e] -------------------------------------------------------------------------------- diff --git a/libs/wire-api/src/Wire/API/User/Client.hs b/libs/wire-api/src/Wire/API/User/Client.hs index e8b8f0574c9..7e757dd420c 100644 --- a/libs/wire-api/src/Wire/API/User/Client.hs +++ b/libs/wire-api/src/Wire/API/User/Client.hs @@ -94,7 +94,6 @@ import qualified Data.Map.Strict as Map import Data.Misc (Latitude (..), Location, Longitude (..), PlainTextPassword (..), latitude, location, longitude, modelLocation) import Data.Qualified import Data.Schema -import qualified Data.Semigroup as Semigroup import qualified Data.Set as Set import Data.Swagger hiding (Schema, ToSchema, schema) import qualified Data.Swagger as Swagger @@ -316,7 +315,7 @@ newtype QualifiedUserClientPrekeyMap = QualifiedUserClientPrekeyMap deriving stock (Eq, Show) deriving newtype (Arbitrary) deriving (FromJSON, ToJSON, Swagger.ToSchema) via Schema QualifiedUserClientPrekeyMap - deriving (Semigroup, Monoid) via (QualifiedUserClientMap (Semigroup.Option (Semigroup.First Prekey))) + deriving (Semigroup, Monoid) via (QualifiedUserClientMap (Alt Maybe Prekey)) instance ToSchema QualifiedUserClientPrekeyMap where schema = diff --git a/libs/wire-api/wire-api.cabal b/libs/wire-api/wire-api.cabal index 255c0440651..e4edd396aa5 100644 --- a/libs/wire-api/wire-api.cabal +++ b/libs/wire-api/wire-api.cabal @@ -266,6 +266,8 @@ library , servant-swagger , servant-swagger-ui , singletons + , singletons-base + , singletons-th , sop-core , string-conversions , swagger >=0.1 diff --git a/nix/haskell-pins.nix b/nix/haskell-pins.nix index 900d716ce93..33026ee33d9 100644 --- a/nix/haskell-pins.nix +++ b/nix/haskell-pins.nix @@ -113,8 +113,8 @@ let bloodhound = { src = fetchgit { url = "https://github.com/wireapp/bloodhound"; - rev = "c68e6d877d87988331bf391ed16780383a58eefa"; - sha256 = "0fr5xgq8f1nmcbk8srrhyf4vad4xm5iqr974jgqfg1mg31y85h0x"; + rev = "abf819a4a6ec7601f1e58cb8da13b2fdad377d9e"; + sha256 = "sha256-m1O+F/mOJN5z5WNChmeyHP4dtmLRkl2YnLlTuwzRelk="; }; }; cryptobox-haskell = { @@ -141,8 +141,8 @@ let hspec-wai = { src = fetchgit { url = "https://github.com/wireapp/hspec-wai"; - rev = "0a5142cd3ba48116ff059c041348b817fb7bdb25"; - sha256 = "1yqkla7467fgb23yw689xh15zjn38rkc7ckf18cfalpc2ff5wfq1"; + rev = "6984a06b0c6294677c49d59382d48f975a8733d4"; + sha256 = "sha256-6FLTMMqvL0xFa5zsMnjVAmdpghmdeBl813bWcOyQo5E="; }; }; http-client = { @@ -168,8 +168,8 @@ let saml2-web-sso = { src = fetchgit { url = "https://github.com/wireapp/saml2-web-sso"; - rev = "74371cd775cb98d6cf85f6e182244a3c4fd48702"; - sha256 = "1w23yz2iiayniymk7k4g8gww7268187cayw0c8m3bz2hbnvbyfbc"; + rev = "b79a45ac98b1f592ac18511fce48ed88d2e931c9"; + sha256 = "sha256-g2lbKt3+hToVFQvaHOa9dg4HqAL7YgReo8fy7wQavmY="; }; }; swagger2 = { @@ -181,16 +181,23 @@ let }; cql-io = { src = fetchgit { - url = "https://gitlab.com/axeman/cql-io"; + url = "https://gitlab.com/wireapp/forks/cql-io"; rev = "c2b6aa995b5817ed7c78c53f72d5aa586ef87c36"; - sha256 = "1wncign8ilvqs9qyl6pkz66x2s8dgwhnfdjw82wv38ijmr95di0c"; + sha256 = "sha256-DMRWUq4yorG5QFw2ZyF/DWnRjfnzGupx0njTiOyLzPI="; }; }; swagger = { src = fetchgit { - url = "https://gitlab.com/axeman/swagger"; + url = "https://gitlab.com/wireapp/forks/swagger.git"; rev = "e2d3f5b5274b8d8d301b5377b0af4319cea73f9e"; - sha256 = "1zj3fqlvcvp9s0myb98b6s9mpmiqamyxn2d3jan55irdgm53prmv"; + sha256 = "sha256-u+Y7Sn0tx1KskqMJ231VONZbkzYLpeUr0Olutil2Q/4="; + }; + }; + wai-predicates = { + src = fetchgit { + url = "https://gitlab.com/wireapp/forks/wai-predicates.git"; + rev = "ff95282a982ab45cced70656475eaf2cefaa26ea"; + sha256 = "sha256-x2XSv2+/+DG9FXN8hfUWGNIO7V4iBhlzYz19WWKaLKQ="; }; }; wai-routing = { @@ -218,23 +225,35 @@ let sha256 = "sha256-yiw6hg3guRWS6CVdrUY8wyIDxoqfGjIVMrEtP+Fys0Y="; }; }; + # Not tested/relased yet + # https://github.com/dylex/invertible/commit/e203c6a729fde87b1f903c3f468f739a085fb446 + invertible = { + src = fetchgit { + url = "https://github.com/dylex/invertible.git"; + rev = "e203c6a729fde87b1f903c3f468f739a085fb446"; + sha256 = "sha256-G6PX5lpU18oWLkwIityN4Hs0HuwQrq9T51kxbsdpK3M="; + }; + }; + tinylog = { + src = fetchgit { + url = "https://gitlab.com/wireapp/forks/tinylog.git"; + rev = "9609104263e8cd2a631417c1c3ef23e090de0d09"; + sha256 = "sha256-htEIJY+LmIMACVZrflU60+X42/g14NxUyFM7VJs4E6w="; + }; + }; }; hackagePins = { wai-route = { version = "0.4.0"; sha256 = "sha256-DSMckKIeVE/buSMg8Mq+mUm1bYPYB7veA11Ns7vTBbc="; }; - partial-isomorphisms = { - version = "0.2.2.1"; - sha256 = "sha256-TdsLB0ueaUUllLdvcGu3YNQXCfGRRk5WxP3deHEbHGI="; - }; kind-generics = { - version = "0.4.1.2"; - sha256 = "sha256-orDfC5+QXRlAMVaqAhT1Fo7Eh/AnobROWeliZqEAXZU="; + version = "0.4.1.4"; + sha256 = "sha256-vH4YO/2NtaLDn1pyAQYK9vC5dD0bpdk26aH5sA9+UT8="; }; kind-generics-th = { - version = "0.2.2.2"; - sha256 = "sha256-nPuRq19UGVXe4YrITAZcF+U4TUBo5APMT2Nh9NqIkxQ="; + version = "0.2.2.3"; + sha256 = "sha256-LH2Wpo0v+RQSsvVbosuB99ekSzqsqMNjrd+w/B0SOUs="; }; polysemy = { version = "1.8.0.0"; @@ -248,18 +267,26 @@ let version = "0.4.3.1"; sha256 = "sha256-0vkLYNZISr3fmmQvD8qdLkn2GHc80l1GzJuOmqjqXE4="; }; - singletons = { - version = "2.7"; - sha256 = "sha256-q7yc/wyGSyYI0KdgHgRi0WISv9WEibxQ5yM7cSjXS2s="; - }; - th-desugar = { - version = "1.11"; - sha256 = "sha256-07sUW1ufEM7Xqv6C2rlFGI5CDO5cchDOND7QFstKu5g="; - }; one-liner = { version = "1.0"; sha256 = "sha256-dv/W8hIPoHVevxiiCb6OfeP53O/9HPgUiqOHGSNb/pk="; }; + singletons = { + version = "3.0.1"; + sha256 = "sha256-ixHWZae6AxjRUldMgpYolXBGsOUT5ZVIw9HZkxrhHQ0="; + }; + singletons-base = { + version = "3.1"; + sha256 = "sha256-SjpkQofdDMrUMi9cHNF5eyqic7WMAhWNqrKr4ip1RNs="; + }; + singletons-th = { + version = "3.1"; + sha256 = "sha256-34nyestxt8KNTSlmr1Y8nElNXa/wZ1+fuLEUVjZX8dk="; + }; + th-desugar = { + version = "1.13"; + sha256 = "sha256-xiAeSM2umcfsz5+mcW+oGKf/EmzvH0ch0lHoKBGzW9I="; + }; }; # Name -> Source -> Maybe Subpath -> Drv mkGitDrv = name: src: subpath: diff --git a/nix/manual-overrides.nix b/nix/manual-overrides.nix index 1d5e8c8a448..024308d4191 100644 --- a/nix/manual-overrides.nix +++ b/nix/manual-overrides.nix @@ -24,9 +24,11 @@ hself: hsuper: { servant-multipart = hlib.doJailbreak hsuper.servant-multipart; hashtables = hsuper.hashtables_1_3; quickcheck-state-machine = hlib.dontCheck hsuper.quickcheck-state-machine; - quickcheck-arbitrary-template = hlib.markUnbroken (hsuper.quickcheck-arbitrary-template); wai-middleware-prometheus = hlib.doJailbreak hsuper.wai-middleware-prometheus; one-liner = hlib.doJailbreak hsuper.one-liner; + type-errors = hlib.dontCheck hsuper.type-errors; + binary-parsers = hlib.doJailbreak hsuper.binary-parsers; + generic-data = hsuper.generic-data_1_0_0_0; # Some test seems to be broken hsaml2 = hlib.dontCheck hsuper.hsaml2; @@ -36,6 +38,8 @@ hself: hsuper: { cql-io = hlib.dontCheck hsuper.cql-io; # Needs network access to running ES + # also the test suite doesn't compile https://github.com/NixOS/nixpkgs/pull/167957 + # due to related broken quickcheck-arbitrary-template bloodhound = hlib.dontCheck hsuper.bloodhound; # These tests require newer version on hspec-wai, which doesn't work with some of the wire-server packages. @@ -61,4 +65,7 @@ hself: hsuper: { # Postie has been fixed upstream (master) postie = hlib.markUnbroken (hlib.doJailbreak hsuper.postie); + + # This would not be necessary if we could pull revision -r1 from 0.2.2.3 + kind-generics-th = hlib.doJailbreak hsuper.kind-generics-th; } diff --git a/nix/wire-server.nix b/nix/wire-server.nix index 586338588fa..99dc0caaafd 100644 --- a/nix/wire-server.nix +++ b/nix/wire-server.nix @@ -145,7 +145,7 @@ let ) executablesMap; - hPkgs = localMods@{ enableOptimization, enableDocs, enableTests }: pkgs.haskell.packages.ghc8107.override { + hPkgs = localMods@{ enableOptimization, enableDocs, enableTests }: pkgs.haskell.packages.ghc92.override { overrides = lib.composeManyExtensions [ pinnedPackages (localPackages localMods) @@ -363,7 +363,7 @@ in devEnv = pkgs.buildEnv { name = "wire-server-dev-env"; paths = commonTools ++ [ - (pkgs.haskell-language-server.override { supportedGhcVersions = [ "8107" ]; }) + (pkgs.haskell-language-server.override { supportedGhcVersions = [ "92" ]; }) pkgs.ghcid pkgs.cfssl pkgs.kind diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index bdcf6859313..c62128ab367 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -326,7 +326,7 @@ servantSitemap = :<|> Named @"get-calls-config-v2" Calling.getCallsConfigV2 systemSettingsAPI :: ServerT SystemSettingsAPI (Handler r) - systemSettingsAPI = Named @ "get-system-settings" getSystemSettings + systemSettingsAPI = Named @"get-system-settings" getSystemSettings -- Note [ephemeral user sideeffect] -- If the user is ephemeral and expired, it will be removed upon calling diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 84ea17a47a3..5a9782dbc0e 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -78,6 +78,7 @@ import qualified Data.Swagger.Build.Api as Doc import qualified Data.Text.Ascii as Ascii import qualified Data.Text.Encoding as Text import qualified Data.ZAuth.Token as ZAuth +import GHC.TypeNats import Imports import Network.HTTP.Types.Status import Network.Wai (Response) @@ -1232,7 +1233,7 @@ maybeInvalidBot = maybe (throwStd invalidBot) pure maybeInvalidUser :: Monad m => Maybe a -> (ExceptT Error m) a maybeInvalidUser = maybe (throwStd (errorToWai @'E.InvalidUser)) pure -rangeChecked :: (Within a n m, Monad monad) => a -> (ExceptT Error monad) (Range n m a) +rangeChecked :: (KnownNat n, KnownNat m, Within a n m, Monad monad) => a -> (ExceptT Error monad) (Range n m a) rangeChecked = either (throwStd . invalidRange . fromString) pure . checkedEither invalidServiceKey :: Wai.Error diff --git a/services/brig/test/integration/API/User/Util.hs b/services/brig/test/integration/API/User/Util.hs index 1428b66ec04..872f9037685 100644 --- a/services/brig/test/integration/API/User/Util.hs +++ b/services/brig/test/integration/API/User/Util.hs @@ -40,6 +40,7 @@ import qualified Data.ByteString.Lazy as LB import Data.Domain import Data.Handle (Handle (Handle)) import Data.Id hiding (client) +import Data.Kind import qualified Data.List1 as List1 import Data.Misc (PlainTextPassword (..)) import Data.Qualified @@ -539,7 +540,7 @@ setTeamSndFactorPasswordChallenge galley tid status = do put (galley . paths ["i", "teams", toByteString' tid, "features", featureNameBS @Public.SndFactorPasswordChallengeConfig] . contentJson . body js) !!! const 200 === statusCode setTeamFeatureLockStatus :: - forall (cfg :: *) m. + forall (cfg :: Type) m. ( MonadCatch m, MonadIO m, MonadHttp m, diff --git a/services/cargohold/src/CargoHold/API/Public.hs b/services/cargohold/src/CargoHold/API/Public.hs index 67821235c9d..6e2ff128302 100644 --- a/services/cargohold/src/CargoHold/API/Public.hs +++ b/services/cargohold/src/CargoHold/API/Public.hs @@ -28,6 +28,7 @@ import Data.ByteString.Builder import qualified Data.ByteString.Lazy as LBS import Data.Domain import Data.Id +import Data.Kind import Data.Qualified import Imports hiding (head) import qualified Network.HTTP.Types as HTTP @@ -96,7 +97,7 @@ instance HasLocation 'ProviderPrincipalTag where assetKeyToText (tUnqualified key) ] -class HasLocation tag => MakePrincipal (tag :: PrincipalTag) (id :: *) | id -> tag, tag -> id where +class HasLocation tag => MakePrincipal (tag :: PrincipalTag) (id :: Type) | id -> tag, tag -> id where mkPrincipal :: id -> V3.Principal instance MakePrincipal 'UserPrincipalTag (Local UserId) where diff --git a/services/federator/src/Federator/Response.hs b/services/federator/src/Federator/Response.hs index 7cc670ad3d0..fa71bab2764 100644 --- a/services/federator/src/Federator/Response.hs +++ b/services/federator/src/Federator/Response.hs @@ -27,6 +27,7 @@ where import Control.Lens import Control.Monad.Codensity import Data.ByteString.Builder +import Data.Kind import Federator.Discovery import Federator.Env import Federator.Error @@ -55,7 +56,7 @@ import Wire.Sem.Logger.TinyLog defaultHeaders :: [HTTP.Header] defaultHeaders = [("Content-Type", "application/json")] -class ErrorEffects (ee :: [*]) r where +class ErrorEffects (ee :: [Type]) r where type Row ee :: EffectRow runWaiErrors :: Sem (Append (Row ee) r) Wai.Response -> diff --git a/services/galley/default.nix b/services/galley/default.nix index 6b48dee10bf..812fc3e7aa4 100644 --- a/services/galley/default.nix +++ b/services/galley/default.nix @@ -86,6 +86,7 @@ , servant-swagger , servant-swagger-ui , singletons +, singletons-th , sop-core , split , ssl-util @@ -206,6 +207,7 @@ mkDerivation { servant-swagger servant-swagger-ui singletons + singletons-th sop-core split ssl-util @@ -307,6 +309,7 @@ mkDerivation { servant-server servant-swagger singletons + singletons-th sop-core ssl-util string-conversions diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index 955c3ac159e..cba743b7195 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -257,6 +257,7 @@ library , servant-swagger , servant-swagger-ui , singletons + , singletons-th , sop-core , split >=0.2 , ssl-util >=0.1 @@ -503,6 +504,7 @@ executable galley-integration , servant-server , servant-swagger , singletons + , singletons-th , sop-core , ssl-util , string-conversions diff --git a/services/galley/src/Galley/API/LegalHold/Conflicts.hs b/services/galley/src/Galley/API/LegalHold/Conflicts.hs index f9e3e07126a..dfcda4e1c9b 100644 --- a/services/galley/src/Galley/API/LegalHold/Conflicts.hs +++ b/services/galley/src/Galley/API/LegalHold/Conflicts.hs @@ -14,6 +14,7 @@ -- -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . +{-# OPTIONS_GHC -Wno-overlapping-patterns #-} module Galley.API.LegalHold.Conflicts where diff --git a/services/galley/src/Galley/API/Push.hs b/services/galley/src/Galley/API/Push.hs index 1e17062e67b..73bc8aed6a8 100644 --- a/services/galley/src/Galley/API/Push.hs +++ b/services/galley/src/Galley/API/Push.hs @@ -46,7 +46,6 @@ import Galley.Data.Services import Galley.Effects.ExternalAccess import Galley.Effects.GundeckAccess hiding (Push) import Galley.Intra.Push -import Galley.Types.Conversations.Members import Gundeck.Types.Push.V2 (RecipientClients (..)) import Imports import Polysemy @@ -83,10 +82,6 @@ newBotPush b e = NormalMessagePush {userPushes = mempty, botPushes = pure (b, e) type BotMap = Map UserId BotMember -type family LocalMemberMap (t :: MessageType) = (m :: *) | m -> t where - LocalMemberMap 'NormalMessage = Map UserId LocalMember - LocalMemberMap 'Broadcast = () - type family MessagePushEffects (t :: MessageType) :: [Effect] type instance MessagePushEffects 'NormalMessage = '[ExternalAccess, GundeckAccess, TinyLog] diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index be179c9c992..10a30bce7b3 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -40,7 +40,7 @@ import Control.Lens import Data.Bifunctor (second) import Data.ByteString.Conversion (toByteString') import Data.Id -import Data.Kind (Constraint) +import Data.Kind import Data.Proxy (Proxy (Proxy)) import Data.Qualified (Local, tUnqualified) import Data.Schema @@ -86,7 +86,7 @@ import Wire.Sem.Paging.Cassandra data DoAuth = DoAuth UserId | DontDoAuth -- | Don't export methods of this typeclass -class GetFeatureConfig (db :: *) cfg where +class GetFeatureConfig (db :: Type) cfg where type GetConfigForTeamConstraints db cfg (r :: EffectRow) :: Constraint type GetConfigForTeamConstraints db cfg (r :: EffectRow) = (FeaturePersistentConstraint db cfg, Members '[Input Opts, TeamFeatureStore db] r) @@ -143,7 +143,7 @@ class GetFeatureConfig (db :: *) cfg where getConfigForUser = genericGetConfigForUser @db -- | Don't export methods of this typeclass -class GetFeatureConfig (db :: *) cfg => SetFeatureConfig (db :: *) cfg where +class GetFeatureConfig (db :: Type) cfg => SetFeatureConfig (db :: Type) cfg where type SetConfigForTeamConstraints db cfg (r :: EffectRow) :: Constraint type SetConfigForTeamConstraints db cfg (r :: EffectRow) = () @@ -338,7 +338,7 @@ updateLockStatus tid lockStatus = do -- Here we explicitly return the team setting if the user is a team member. -- In `getConfigForUser` this is mostly also the case. But there are exceptions, e.g. `ConferenceCallingConfig` getFeatureStatusForUser :: - forall (db :: *) cfg r. + forall (db :: Type) cfg r. ( Members '[ ErrorS 'NotATeamMember, ErrorS OperationDenied, @@ -572,7 +572,7 @@ genericGetConfigForUser uid = do genericGetConfigForTeam @db tid persistAndPushEvent :: - forall (db :: *) cfg r. + forall (db :: Type) cfg r. ( IsFeatureConfig cfg, KnownSymbol (FeatureSymbol cfg), ToSchema cfg, diff --git a/services/galley/src/Galley/Effects/TeamFeatureStore.hs b/services/galley/src/Galley/Effects/TeamFeatureStore.hs index 78ca78b8b14..86ea0ed352a 100644 --- a/services/galley/src/Galley/Effects/TeamFeatureStore.hs +++ b/services/galley/src/Galley/Effects/TeamFeatureStore.hs @@ -29,13 +29,13 @@ module Galley.Effects.TeamFeatureStore where import Data.Id -import Data.Kind (Constraint) +import Data.Kind import Data.Proxy import Imports import Polysemy import Wire.API.Team.Feature -type family FeaturePersistentConstraint db :: * -> Constraint +type family FeaturePersistentConstraint db :: Type -> Constraint data TeamFeatureStore db m a where -- the proxy argument makes sure that makeSem below generates type-inference-friendly code diff --git a/services/galley/src/Galley/Validation.hs b/services/galley/src/Galley/Validation.hs index 4ff62e03e23..f87db6df4bf 100644 --- a/services/galley/src/Galley/Validation.hs +++ b/services/galley/src/Galley/Validation.hs @@ -28,18 +28,19 @@ where import Control.Lens import Data.Range +import GHC.TypeNats import Galley.API.Error import Galley.Options import Imports import Polysemy import Polysemy.Error -rangeChecked :: (Member (Error InvalidInput) r, Within a n m) => a -> Sem r (Range n m a) +rangeChecked :: (KnownNat n, KnownNat m, Member (Error InvalidInput) r, Within a n m) => a -> Sem r (Range n m a) rangeChecked = either throwErr pure . checkedEither {-# INLINE rangeChecked #-} rangeCheckedMaybe :: - (Member (Error InvalidInput) r, Within a n m) => + (Member (Error InvalidInput) r, KnownNat n, KnownNat m, Within a n m) => Maybe a -> Sem r (Maybe (Range n m a)) rangeCheckedMaybe Nothing = pure Nothing diff --git a/services/galley/test/integration/API/Federation/Util.hs b/services/galley/test/integration/API/Federation/Util.hs index 727a97c4f28..f29d031927f 100644 --- a/services/galley/test/integration/API/Federation/Util.hs +++ b/services/galley/test/integration/API/Federation/Util.hs @@ -17,6 +17,7 @@ module API.Federation.Util (mkHandler) where +import Data.Kind import Data.SOP import Data.String.Conversions (cs) import GHC.TypeLits @@ -59,7 +60,7 @@ trivialNamedHandler = Named (trivialHandler @api (symbolVal (Proxy @name))) -- | Generate a servant handler from an incomplete list of handlers of named -- endpoints. -class PartialAPI (api :: *) (hs :: *) where +class PartialAPI (api :: Type) (hs :: Type) where mkHandler :: hs -> Server api instance diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 72cb13c55bc..154d1d83489 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -605,7 +605,7 @@ testRemoveBindingTeamMember ownerHasPassword = do mem1 <- Util.addUserToTeam owner tid assertQueue "team member join" $ tUpdate 3 [ownerWithPassword, owner] refreshIndex - Util.connectUsers owner (singleton mext) + Util.connectUsers owner (List1.singleton mext) cid1 <- Util.createTeamConv owner tid [mem1 ^. userId, mext] (Just "blaa") Nothing Nothing when ownerHasPassword $ do -- Deleting from a binding team with empty body is invalid @@ -1987,7 +1987,7 @@ postCryptoBroadcastMessage100OrMaxConns bcast = do where createAndConnectUserWhileLimitNotReached alice remaining acc pk = do (uid, cid) <- randomUserWithClient pk - (r1, r2) <- List1.head <$> connectUsersUnchecked alice (singleton uid) + (r1, r2) <- List1.head <$> connectUsersUnchecked alice (List1.singleton uid) case (statusCode r1, statusCode r2, remaining, acc) of (201, 200, 0, []) -> error "Need to connect with at least 1 user" (201, 200, 0, x : xs) -> pure (x, xs) diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index adfc6d1b290..ba66f58e6c0 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -48,6 +48,7 @@ import qualified Data.Handle as Handle import qualified Data.HashMap.Strict as HashMap import Data.Id import Data.Json.Util hiding ((#)) +import Data.Kind import Data.LegalHold (defUserLegalHoldStatus) import Data.List.NonEmpty (NonEmpty) import Data.List1 as List1 @@ -72,6 +73,7 @@ import Data.UUID.V4 import Federator.MockServer (FederatedRequest (..)) import qualified Federator.MockServer as Mock import GHC.TypeLits (KnownSymbol) +import GHC.TypeNats import Galley.Intra.User (chunkify) import qualified Galley.Options as Opts import qualified Galley.Run as Run @@ -724,7 +726,7 @@ postConvWithRemoteUsers u n = Maybe (Range n m Text) -> Maybe (Range n m Text) + setName :: (KnownNat n, KnownNat m, Within Text n m) => Maybe (Range n m Text) -> Maybe (Range n m Text) setName Nothing = checked "federated gossip" setName x = x @@ -2634,7 +2636,7 @@ type CombinedBrigAndGalleyAPI = FedApi 'Brig :<|> FedApi 'Galley -- Starts a servant Application in Network.Wai.Test session and runs the -- FederatedRequest against it. makeFedRequestToServant :: - forall (api :: *). + forall (api :: Type). HasServer api '[] => Domain -> Server api -> diff --git a/services/gundeck/src/Gundeck/Options.hs b/services/gundeck/src/Gundeck/Options.hs index 95eb235f41d..8e23457e9d1 100644 --- a/services/gundeck/src/Gundeck/Options.hs +++ b/services/gundeck/src/Gundeck/Options.hs @@ -87,10 +87,6 @@ data MaxConcurrentNativePushes = MaxConcurrentNativePushes } deriving (Show, Generic) -deriveFromJSON toOptionFieldName ''Settings - -makeLenses ''Settings - deriveFromJSON toOptionFieldName ''MaxConcurrentNativePushes makeLenses ''MaxConcurrentNativePushes @@ -113,6 +109,10 @@ deriveFromJSON toOptionFieldName ''RedisEndpoint makeLenses ''RedisEndpoint +makeLenses ''Settings + +deriveFromJSON toOptionFieldName ''Settings + data Opts = Opts { -- | Hostname and port to bind to _optGundeck :: !Endpoint, diff --git a/tools/db/assets/src/Assets/Lib.hs b/tools/db/assets/src/Assets/Lib.hs index c4947b4d52d..036b31ae6b8 100644 --- a/tools/db/assets/src/Assets/Lib.hs +++ b/tools/db/assets/src/Assets/Lib.hs @@ -188,5 +188,4 @@ instance Semigroup Result where instance Monoid Result where mempty = Result 0 0 [] - mappend (Result n1 v1 i1) (Result n2 v2 i2) = - Result (n1 + n2) (v1 + v2) (i1 <> i2) + mappend = (<>) diff --git a/tools/stern/src/Stern/API.hs b/tools/stern/src/Stern/API.hs index a849241aeb6..31b67199acd 100644 --- a/tools/stern/src/Stern/API.hs +++ b/tools/stern/src/Stern/API.hs @@ -207,7 +207,7 @@ searchOnBehalf :: UserId -> Maybe T.Text -> Maybe Int32 -> Handler (SearchResult searchOnBehalf uid (fromMaybe "" -> q) - (fromMaybe (unsafeRange 10) . checked @Int32 @1 @100 . fromMaybe 10 -> s) = + (fromMaybe (unsafeRange 10) . checked @1 @100 @Int32 . fromMaybe 10 -> s) = Intra.getContacts uid q (fromRange s) revokeIdentity :: Maybe Email -> Maybe Phone -> Handler NoContent diff --git a/tools/stern/src/Stern/API/Routes.hs b/tools/stern/src/Stern/API/Routes.hs index 5fabd0ffe0b..ac61085d1de 100644 --- a/tools/stern/src/Stern/API/Routes.hs +++ b/tools/stern/src/Stern/API/Routes.hs @@ -32,6 +32,7 @@ import Control.Monad.Trans.Except import qualified Data.Aeson as A import Data.Handle import Data.Id +import Data.Kind import qualified Data.Schema as Schema import qualified Data.Swagger as S import Imports hiding (head) @@ -422,7 +423,7 @@ doubleMaybeToEither _ (Just a) Nothing = pure $ Left a doubleMaybeToEither _ Nothing (Just b) = pure $ Right b doubleMaybeToEither msg _ _ = throwE $ mkError status400 "either-params" ("Must use exactly one of two query params: " <> msg) -type MkFeatureGetRoute (feature :: *) = +type MkFeatureGetRoute (feature :: Type) = Summary "Shows whether a feature flag is enabled or not for a given team." :> "teams" :> Capture "tid" TeamId @@ -430,7 +431,7 @@ type MkFeatureGetRoute (feature :: *) = :> FeatureSymbol feature :> Get '[JSON] (WithStatus feature) -type MkFeaturePutRouteTrivialConfigNoTTL (feature :: *) = +type MkFeaturePutRouteTrivialConfigNoTTL (feature :: Type) = Summary "Disable / enable status for a given feature / team" :> "teams" :> Capture "tid" TeamId @@ -439,7 +440,7 @@ type MkFeaturePutRouteTrivialConfigNoTTL (feature :: *) = :> QueryParam' [Required, Strict] "status" FeatureStatus :> Put '[JSON] NoContent -type MkFeaturePutRouteTrivialConfigWithTTL (feature :: *) = +type MkFeaturePutRouteTrivialConfigWithTTL (feature :: Type) = Summary "Disable / enable status for a given feature / team" :> Description "team feature time to live, given in days, or 'unlimited' (default). only available on *some* features!" :> "teams" @@ -450,7 +451,7 @@ type MkFeaturePutRouteTrivialConfigWithTTL (feature :: *) = :> QueryParam' [Required, Strict, Description "team feature time to live, given in days, or 'unlimited' (default)."] "ttl" FeatureTTLDays :> Put '[JSON] NoContent -type MkFeaturePutRoute (feature :: *) = +type MkFeaturePutRoute (feature :: Type) = Summary "Disable / enable feature flag for a given team" :> "teams" :> Capture "tid" TeamId