packages feed

haskell-admin-core (empty) → 1.0.0.0

raw patch · 18 files changed

+507/−0 lines, 18 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, haskell-admin-core, hspec, hspec-wai, http-types, servant, servant-server, wai, wai-cors, wai-extra, wai-middleware-bearer, word8

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for haskell-admin-core++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2022 Martin Bednar++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,3 @@+# haskell-admin-core++This package provides the core implementation of Haskell Admin without any components. It can be used to develop new components.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ haskell-admin-core.cabal view
@@ -0,0 +1,83 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           haskell-admin-core+version:        1.0.0.0+synopsis:       Core Modules of Haskell Admin+description:    Please see the README on GitHub at <https://github.com/martin-bednar/haskell-admin#readme>+category:       Remote Management, Web+homepage:       https://github.com/martin-bednar/haskell-admin#readme+bug-reports:    https://github.com/martin-bednar/haskell-admin/issues+author:         Martin Bednar+maintainer:     bednam17@fit.cvut.cz+copyright:      2022 Martin Bednar+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/martin-bednar/haskell-admin++library+  exposed-modules:+      Admin.API+      Admin.Component.EmptyComponent+      Admin.Components+      Admin.Components.Component+      Admin.Components.ComponentDescription+      Admin.Components.ComponentList+      Admin.Components.ComponentsClass+      Admin.Components.Internal.TypeLevel+      Admin.Server+      Admin.Server.Middlewares+  other-modules:+      Paths_haskell_admin_core+  hs-source-dirs:+      src+  ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -Wmonomorphism-restriction -Wmissing-home-modules -Widentities -Wredundant-constraints -Wmissing-export-lists+  build-depends:+      aeson+    , base >=4.7 && <5+    , bytestring+    , http-types+    , servant+    , servant-server+    , wai+    , wai-cors+    , wai-middleware-bearer+    , word8+  default-language: Haskell2010++test-suite haskell-admin-core-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Spec.Admin.Components+      Spec.Admin.Server+      Paths_haskell_admin_core+  hs-source-dirs:+      test+  ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wpartial-fields -Wmonomorphism-restriction -Wmissing-home-modules -Widentities -Wredundant-constraints -Wmissing-export-lists -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      aeson+    , base >=4.7 && <5+    , bytestring+    , haskell-admin-core+    , hspec+    , hspec-wai+    , http-types+    , servant+    , servant-server+    , wai+    , wai-cors+    , wai-extra+    , wai-middleware-bearer+    , word8+  default-language: Haskell2010
+ src/Admin/API.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE StarIsType #-}+{-# LANGUAGE PolyKinds #-}++module Admin.API+  ( AdminAPI+  , ComponentsAPI+  ) where++import Admin.Components.ComponentDescription (ComponentDescription)+import Data.Version+import Servant++-- | The type-level definition of the Haskell Admin API+-- with server components as a parameter+type AdminAPI components = Root :<|> "components" :> ComponentsAPI components++type Root = Get '[ JSON] NoContent :<|> "api_version" :> Get '[ JSON] Version++type ComponentsAPI inner = Get '[ JSON] [ComponentDescription] :<|> inner
+ src/Admin/Component/EmptyComponent.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE DataKinds #-}++module Admin.Component.EmptyComponent+  ( emptyComponent+  ) where++import Admin.Components+import Data.Version (makeVersion)+import Servant++emptyComponent :: Component "empty" EmptyAPI+emptyComponent = Component {server = emptyServer, version = makeVersion [1]}
+ src/Admin/Components.hs view
@@ -0,0 +1,9 @@+module Admin.Components+  ( module Admin.Components.Component+  , module Admin.Components.ComponentList+  , module Admin.Components.ComponentsClass+  ) where++import Admin.Components.Component+import Admin.Components.ComponentList+import Admin.Components.ComponentsClass
+ src/Admin/Components/Component.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Admin.Components.Component+  ( Component(..)+  ) where++import Data.Version+import GHC.TypeLits (Symbol)+import Servant++data Component (name :: Symbol) api =+  Component+    { server :: Server api+    , version :: Version+    }
+ src/Admin/Components/ComponentDescription.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE ExplicitForAll #-}++module Admin.Components.ComponentDescription where++import Data.Aeson (FromJSON, ToJSON)+import Data.Version (Version)+import GHC.Generics (Generic)++data ComponentDescription =+  ComponentDescription+    { componentName :: String+    , componentVersion :: Version+    }+  deriving (Read, Show, Eq, Generic, ToJSON, FromJSON)
+ src/Admin/Components/ComponentList.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Admin.Components.ComponentList+  ( ComponentList(..)+  , namesOf+  , descriptionsOf+  , empty+  ) where++import Admin.Components.ComponentDescription+import Admin.Components.Internal.TypeLevel (ManySymbolVal, manySymbolVal)+import Data.Version+import GHC.TypeLits (Symbol)+import Servant++data ComponentList (names :: [Symbol]) apis =+  ComponentList+    { serveAll :: Server apis+    , versions :: [Version]+    }++namesOf ::+     forall names apis. (ManySymbolVal names)+  => ComponentList names apis+  -> [String]+namesOf _ = manySymbolVal (Proxy :: Proxy names)++descriptionsOf ::+     forall names apis. (ManySymbolVal names)+  => ComponentList names apis+  -> [ComponentDescription]+descriptionsOf cs = zipWith ComponentDescription (namesOf cs) (versions cs)++empty :: ComponentList '[] EmptyAPI+empty = ComponentList emptyServer [makeVersion [1]]
+ src/Admin/Components/ComponentsClass.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Admin.Components.ComponentsClass+  ( with+  , Components(..)+  ) where++import Admin.Components.Component+import Admin.Components.ComponentDescription+import Admin.Components.ComponentList+import Admin.Components.Internal.TypeLevel+import Data.Version+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)+import Servant++type ToServantAPI names api = (ManySymbolVal names, HasServer api '[])++class (ToServantAPI names apis) =>+      Components a (names :: [Symbol]) apis+  | a -> names apis+  where+  serveAll' :: a -> Server apis+  describe :: a -> [ComponentDescription]++instance (KnownSymbol name, HasServer api '[]) =>+         Components (Component name api) '[ name] (name :> api) where+  serveAll' c = server c+  describe c =+    let n = symbolVal (Proxy :: Proxy name)+        v = version c+     in [ComponentDescription n v]++instance (ToServantAPI names apis) =>+         Components (ComponentList names apis) names apis where+  serveAll' c = serveAll c+  describe c = descriptionsOf c++getVersions :: Components a names apis => a -> [Version]+getVersions = map componentVersion . describe++infixr 9 `with`++-- | Compose Components+--+-- This function combines a 'Component' with another 'Component',+-- or with a 'ComponentList', producing a 'ComponentList'.+--+-- It collects the names of the components,+-- and keeps the expected API structure.+--+-- Usage:+-- >>> componentA `with` componentB `with` componentC+with ::+     (Components a names apis)+  => Component name api+  -> a+  -> ComponentList (name : names) ((name :> api) :<|> apis)+with new lst =+  ComponentList (server new :<|> serveAll' lst) (version new : getVersions lst)
+ src/Admin/Components/Internal/TypeLevel.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE StarIsType #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Admin.Components.Internal.TypeLevel+  ( NamesOf+  , NameOf+  , ApisOf+  , ApiOf+  , ManySymbolVal(..)+  ) where++import Admin.Components.Component (Component(..))+import Data.Data (Proxy(..))+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)+import Servant.API ((:<|>), (:>))++type family NameOf (x :: *) where+  NameOf (Component name api) = (name :: Symbol)++type family ApiOf (x :: *) where+  ApiOf (Component (name :: Symbol) api) = name :> api++type family NamesOf (xs :: [*]) where+  NamesOf '[] = '[]+  NamesOf (x ': xs) = NameOf x ': NamesOf xs++type family ApisOf (xs :: [*]) where+  ApisOf (x ': '[]) = ApiOf x+  ApisOf (x ': xs) = ApiOf x :<|> ApisOf xs++-- Type class ManySymbolVal+-- Source: https://stackoverflow.com/a/37365020+-- Author: Andrew Thaddeus Martin+class ManySymbolVal (xs :: [Symbol]) where+  manySymbolVal :: proxy xs -> [String]++instance ManySymbolVal '[] where+  manySymbolVal _ = []++instance (KnownSymbol a, ManySymbolVal as) => ManySymbolVal (a ': as) where+  manySymbolVal _ =+    symbolVal (Proxy :: Proxy a) : manySymbolVal (Proxy :: Proxy as)
+ src/Admin/Server.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}++module Admin.Server+  ( adminApp+  , serveAdmin+  ) where++import Admin.API+import Admin.Components+import Admin.Server.Middlewares (middlewares)+import Data.ByteString.Char8 (ByteString)+import Data.Version+import Servant++adminApp :: Components a names api => a -> [ByteString] -> Application+adminApp c =+  let p = (Proxy :: Proxy (AdminAPI api))+   in adminApp' c p++adminApp' ::+     Components a names api+  => a+  -> Proxy (AdminAPI api)+  -> [ByteString]+  -> Application+adminApp' c proxy tokens = withMiddlewares tokens $ serve proxy $ serveAdmin c++withMiddlewares :: [ByteString] -> Application -> Application+withMiddlewares = middlewares++serveAdmin :: Components a names api => a -> Server (AdminAPI api)+serveAdmin c = handleRoot :<|> serveComponents c++serveComponents :: Components a names apis => a -> Server (ComponentsAPI apis)+serveComponents c = return (describe c) :<|> serveAll' c++handleRoot = return NoContent :<|> return (makeVersion [1])
+ src/Admin/Server/Middlewares.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}++module Admin.Server.Middlewares+  ( middlewares+  ) where++import Data.ByteString.Char8 (ByteString)+import Network.Wai (Middleware)+import Network.Wai.Middleware.BearerTokenAuth+import Network.Wai.Middleware.Cors++-- | Middlewares used in Haskell Admin+middlewares :: [ByteString] -> Middleware+middlewares tokens = allowCors . tokenListAuth tokens++allowCors :: Middleware+allowCors = cors (const $ Just policy)++policy :: CorsResourcePolicy+policy =+  simpleCorsResourcePolicy+    {corsRequestHeaders = ["Authorization", "Content-Type"]}
+ test/Spec.hs view
@@ -0,0 +1,16 @@+module Main+  ( main+  ) where++import Test.Hspec++import qualified Spec.Admin.Components+import qualified Spec.Admin.Server++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  describe "Spec.Admin.Components" Spec.Admin.Components.spec+  describe "Spec.Admin.Server" Spec.Admin.Server.spec
+ test/Spec/Admin/Components.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE DataKinds #-}++module Spec.Admin.Components+  ( spec+  ) where++import Admin.Components+import Test.Hspec as Hspec++import Admin.Components.ComponentDescription+  ( ComponentDescription(ComponentDescription)+  )+import Data.Version (makeVersion)+import Servant++components = cA `with` cB++cA :: Component "Comp. A" EmptyAPI+cA = Component {server = emptyServer, version = makeVersion [1]}++cB :: Component "Comp. B" EmptyAPI+cB = Component {server = emptyServer, version = makeVersion [2, 4]}++spec :: Spec+spec = do+  Hspec.describe "Admin.Components.ComponentList" $ do+    Hspec.describe "namesOf" $ do+      it "returns names of all components" $ do+        namesOf components `shouldBe` ["Comp. A", "Comp. B"]+    Hspec.describe "descriptionsOf" $ do+      it "returns descriptions of all components" $ do+        descriptionsOf components `shouldBe`+          [ ComponentDescription "Comp. A" (makeVersion [1])+          , ComponentDescription "Comp. B" (makeVersion [2, 4])+          ]
+ test/Spec/Admin/Server.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}++module Spec.Admin.Server+  ( spec+  ) where++import Admin.Component.EmptyComponent+import Admin.Server+import Data.ByteString+import Network.HTTP.Types.Header+import Network.HTTP.Types.Method+import Network.Wai.Test (SResponse)+import Servant (Application)+import Test.Hspec+import Test.Hspec.Wai++app :: IO Application+app = return $ adminApp emptyComponent ["testApiKey"]++authHeader :: Header+authHeader = ("Authorization", "Bearer testApiKey")++getS :: ByteString -> WaiSession st SResponse+getS path = request methodGet path [authHeader] ""++spec :: Spec+spec =+  with app $ do+    describe "GET /" $ do+      it "responds to the root path" $ do getS "/" `shouldRespondWith` 200+    describe "GET /api_version" $ do+      it "responds with the API version" $ do+        getS "/api_version" `shouldRespondWith` 200 {matchBody = "\"1\""}+    describe "GET /components" $ do+      it "lists components" $ do+        getS "/components" `shouldRespondWith`+          200+            { matchBody =+                "[{\"componentName\":\"empty\",\"componentVersion\":\"1\"}]"+            }+    describe "authorization" $ do+      it "refuses unauthorized requests" $ do+        get "/" `shouldRespondWith` 401+        request methodGet "/" [("Authorization", "Bearer badKey")] "" `shouldRespondWith`+          401