{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} -- | -- Module : Credentials.CLI.Types -- Copyright : (c) 2015-2016 Brendan Hay -- License : Mozilla Public License, v. 2.0. -- Maintainer : Brendan Hay -- Stability : provisional -- Portability : non-portable (GHC extensions) -- module Credentials.CLI.Types where import Control.Monad.Base import Control.Monad.Catch import Control.Monad.Morph (hoist) import Control.Monad.Reader import Control.Monad.Trans.Resource import Credentials import Credentials.CLI.Types.Protocol import Crypto.Random (MonadRandom (..)) import Data.ByteString (ByteString) import Data.ByteString.Builder (Builder) import Data.Conduit import Data.Conduit.Lazy import Data.Data import Data.Functor.Identity (Identity (..)) import Data.List (sort) import Data.Maybe (fromMaybe) import Data.Text (Text) import Network.AWS import Network.AWS.Data import Network.AWS.DynamoDB (dynamoDB) import Network.AWS.Endpoint import Options.Applicative import Options.Applicative.Help.Pretty (Pretty (..), text) import URI.ByteString (Authority (..), Host (..), Port (..), Scheme (..), URI, URIRef (..)) import qualified Data.Attoparsec.Text as A import qualified Data.HashMap.Strict as Map import qualified Data.Text as Text import qualified URI.ByteString as URI data Force = NoPrompt | Prompt data Input = Value !ByteString | Path !FilePath data Mode = Setup | Teardown !Force | List | Insert !KeyId !Context !Name !Input | Select !Context !Name !(Maybe Revision) | Delete !Name !Revision !Force | Truncate !Name !Force data Format = Pretty | JSON | Echo | Print deriving (Eq, Show) instance ToText Format where toText = \case Pretty -> "pretty" JSON -> "json" Echo -> "echo" Print -> "print" instance FromText Format where parser = takeLowerText >>= \case "pretty" -> pure Pretty "json" -> pure JSON "echo" -> pure Echo e -> fromTextError $ "Failure parsing format from: " <> e data Options f = Options { region :: !(f Region) , store :: !(f Store) , format :: !Format , level :: !LogLevel } deriving instance Show (Options Maybe) deriving instance Show (Options Identity) newtype App a = App { unApp :: ReaderT (Options Identity) AWS a } deriving ( Functor , Applicative , Monad , MonadIO , MonadThrow , MonadCatch , MonadMask , MonadReader (Options Identity) , MonadBase IO ) instance MonadAWS App where liftAWS = App . lift instance MonadResource App where liftResourceT = App . liftResourceT instance MonadRandom App where getRandomBytes = liftIO . getRandomBytes runApp :: Env -> Options Identity -> App a -> IO a runApp e c = runResourceT . runAWS e . (`runReaderT` c) . unApp runLazy :: Source App a -> App [a] runLazy = App . lazyConsume . hoist unApp data Store = Table URI DynamoTable instance FromText Store where parser = uriParser instance FromURI Store where fromURI u = Table u <$> fromURI u instance ToText Store where toText = toText . URI.serializeURIRef' . \case Table u _ -> u instance Show Store where show = Text.unpack . toText instance Pretty Store where pretty = text . show instance ToLog Store where build = build . toText defaultOptions :: Options Maybe -> Options Identity defaultOptions x = let r = fromMaybe defaultRegion (region x) s = fromMaybe (defaultStore r) (store x) in x { region = Identity r , store = Identity s } defaultRegion :: Region defaultRegion = Frankfurt defaultStore :: Region -> Store defaultStore r = Table u defaultTable where u = URI s (Just a) ("/" <> toBS defaultTable) mempty Nothing s = Scheme "dynamo" a = Authority Nothing h (Just p) h = Host (_endpointHost e) p = Port (_endpointPort e) e = defaultEndpoint dynamoDB r setStore :: HasEnv a => Options Identity -> a -> a setStore c = configure f where f = case store c of Identity (Table u _) -> g u dynamoDB g u | Just h <- host u = setEndpoint (secure u) h (port u) | otherwise = id (%) :: ToLog a => Builder -> a -> Builder b % x = b <> build x unsafeEnum :: forall a. (Ord a, Data a, ToText a) => [a] unsafeEnum = sort . map fromConstr . dataTypeConstrs $ dataTypeOf val where val :: a val = undefined string :: ToText a => a -> String string = Text.unpack . toText data Pair = Pair Text Text instance FromText Pair where parser = Pair <$> key <*> val where key = A.skipSpace *> A.takeWhile1 (/= '=') val = A.char '=' *> A.takeText fromPairs :: Alternative f => f Pair -> f Context fromPairs f = Context . Map.fromList . map (\(Pair k v) -> (k, v)) <$> many f