{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

module Hercules.CLI.State (commandParser, getProjectAndClient) where

import Conduit (mapC, runConduitRes, sinkLazyBuilder, (.|))
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.Has (Has)
import Hercules.API (ClientAuth, NoContent, enterApiE)
import Hercules.API.Name (Name (Name))
import Hercules.API.State
import Hercules.CLI.Client
import Hercules.CLI.Common (runAuthenticated)
import Hercules.CLI.Options (mkCommand, subparser)
import Hercules.CLI.Project (ProjectPath (projectPathOwner, projectPathProject, projectPathSite), findProjectContextually, projectOption)
import Options.Applicative (auto, bashCompleter, completer, help, long, metavar, option, strOption)
import qualified Options.Applicative as Optparse
import Protolude
import RIO (RIO)
import qualified RIO.ByteString as BS
import Servant.API (HList (HCons), Headers (Headers), ResponseHeader (Header), fromSourceIO)
import Servant.Client (ClientError (ConnectionError))
import Servant.Client.Generic (AsClientT)
import Servant.Client.Internal.HttpClient.Streaming (ClientM)
import Servant.Conduit ()
import qualified Servant.Types.SourceT as Servant

commandParser, getCommandParser, putCommandParser :: Optparse.Parser (IO ())
commandParser :: Parser (IO ())
commandParser =
  Mod CommandFields (IO ()) -> Parser (IO ())
forall a. Mod CommandFields a -> Parser a
subparser
    ( FilePath
-> InfoMod (IO ()) -> Parser (IO ()) -> Mod CommandFields (IO ())
forall a. FilePath -> InfoMod a -> Parser a -> Mod CommandFields a
mkCommand
        FilePath
"get"
        (FilePath -> InfoMod (IO ())
forall a. FilePath -> InfoMod a
Optparse.progDesc FilePath
"Download a state file")
        Parser (IO ())
getCommandParser
        Mod CommandFields (IO ())
-> Mod CommandFields (IO ()) -> Mod CommandFields (IO ())
forall a. Semigroup a => a -> a -> a
<> FilePath
-> InfoMod (IO ()) -> Parser (IO ()) -> Mod CommandFields (IO ())
forall a. FilePath -> InfoMod a -> Parser a -> Mod CommandFields a
mkCommand
          FilePath
"put"
          (FilePath -> InfoMod (IO ())
forall a. FilePath -> InfoMod a
Optparse.progDesc FilePath
"Upload a state file")
          Parser (IO ())
putCommandParser
    )
getCommandParser :: Parser (IO ())
getCommandParser = do
  Maybe ProjectPath
projectMaybe <- Parser ProjectPath -> Parser (Maybe ProjectPath)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser ProjectPath
projectOption
  Text
name <- Parser Text
nameOption
  FilePath
file <- Parser FilePath
fileOption
  Maybe Int
versionMaybe <- Parser Int -> Parser (Maybe Int)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser Int
versionOption
  pure do
    RIO (HerculesClientToken, HerculesClientEnv) () -> IO ()
forall b. RIO (HerculesClientToken, HerculesClientEnv) b -> IO b
runAuthenticated do
      ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient <- Maybe ProjectPath
-> RIO
     (HerculesClientToken, HerculesClientEnv)
     (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe
      -- TODO: version
      ByteString
bytes <- Text
-> (Token
    -> ClientM
         (Headers '[ContentLength, ContentDisposition] (SourceIO RawBytes)))
-> (Either
      ClientError
      (Headers '[ContentLength, ContentDisposition] (SourceIO RawBytes))
    -> IO ByteString)
-> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall r b c.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Text
-> (Token -> ClientM b)
-> (Either ClientError b -> IO c)
-> RIO r c
retryStreamOnFail Text
"state get" (\Token
token -> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
   :- (Summary "Download a state file"
       :> ("state"
           :> (Capture' '[Required, Strict] "stateName" Text
               :> ("data"
                   :> (QueryParam' '[Optional, Strict] "version" Int
                       :> (ClientAuth
                           :> StreamGet
                                NoFraming
                                OctetStream
                                (Headers
                                   '[ContentLength, ContentDisposition] (SourceIO RawBytes))))))))
forall auth f.
ProjectStateResourceGroup auth f
-> f
   :- (Summary "Download a state file"
       :> ("state"
           :> (Capture' '[Required, Strict] "stateName" Text
               :> ("data"
                   :> (QueryParam' '[Optional, Strict] "version" Int
                       :> (auth
                           :> StreamGet
                                NoFraming
                                OctetStream
                                (Headers
                                   '[ContentLength, ContentDisposition] (SourceIO RawBytes))))))))
getStateData ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient Text
name Maybe Int
versionMaybe Token
token) \case
        Left ClientError
e -> ClientError -> IO ByteString
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO ClientError
e
        Right (Headers SourceIO RawBytes
stream HList '[ContentLength, ContentDisposition]
headers) -> do
#if MIN_VERSION_servant(0,20,0)
          s <- fromSourceIO stream
#else
          let s :: ConduitT () RawBytes (ResourceT IO) ()
s = SourceIO RawBytes -> ConduitT () RawBytes (ResourceT IO) ()
forall chunk a. FromSourceIO chunk a => SourceIO chunk -> a
fromSourceIO SourceIO RawBytes
stream
#endif
          ByteString
bl <- ConduitT () Void (ResourceT IO) ByteString -> IO ByteString
forall (m :: * -> *) r.
MonadUnliftIO m =>
ConduitT () Void (ResourceT m) r -> m r
runConduitRes (ConduitT () Void (ResourceT IO) ByteString -> IO ByteString)
-> ConduitT () Void (ResourceT IO) ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ ConduitT () RawBytes (ResourceT IO) ()
s ConduitT () RawBytes (ResourceT IO) ()
-> ConduitT RawBytes Void (ResourceT IO) ByteString
-> ConduitT () Void (ResourceT IO) ByteString
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| (RawBytes -> Builder)
-> ConduitT RawBytes Builder (ResourceT IO) ()
forall (m :: * -> *) a b. Monad m => (a -> b) -> ConduitT a b m ()
mapC (ByteString -> Builder
BB.byteString (ByteString -> Builder)
-> (RawBytes -> ByteString) -> RawBytes -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RawBytes -> ByteString
fromRawBytes) ConduitT RawBytes Builder (ResourceT IO) ()
-> ConduitT Builder Void (ResourceT IO) ByteString
-> ConduitT RawBytes Void (ResourceT IO) ByteString
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| ConduitT Builder Void (ResourceT IO) ByteString
forall (m :: * -> *) o. Monad m => ConduitT Builder o m ByteString
sinkLazyBuilder
          let lenH :: ResponseHeader "Content-Length" Integer
              ResponseHeader h x
ResponseHeader "Content-Length" Integer
lenH `HCons` HList xs
_ = HList '[ContentLength, ContentDisposition]
headers
              actual :: Integer
actual = Int64 -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int64 -> Integer) -> Int64 -> Integer
forall a b. (a -> b) -> a -> b
$ ByteString -> Int64
BL.length ByteString
bl
          case ResponseHeader "Content-Length" Integer
lenH of
            Header Integer
expected ->
              Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Integer
actual Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
/= Integer
expected) do
                ClientError -> IO ()
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (ClientError -> IO ()) -> ClientError -> IO ()
forall a b. (a -> b) -> a -> b
$ SomeException -> ClientError
ConnectionError (SomeException -> ClientError) -> SomeException -> ClientError
forall a b. (a -> b) -> a -> b
$ FatalError -> SomeException
forall e. Exception e => e -> SomeException
toException (FatalError -> SomeException) -> FatalError -> SomeException
forall a b. (a -> b) -> a -> b
$ Text -> FatalError
FatalError (Text -> FatalError) -> Text -> FatalError
forall a b. (a -> b) -> a -> b
$ Text
"Expected " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall a b. (Show a, StringConv FilePath b) => a -> b
show Integer
expected Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" bytes, but got " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall a b. (Show a, StringConv FilePath b) => a -> b
show Integer
actual
            ResponseHeader "Content-Length" Integer
_ -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
          pure (ByteString -> ByteString
BL.toStrict ByteString
bl)
      case FilePath
file of
        FilePath
"-" -> ByteString -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => ByteString -> m ()
BS.putStr ByteString
bytes
        FilePath
_ -> FilePath
-> ByteString -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => FilePath -> ByteString -> m ()
BS.writeFile FilePath
file ByteString
bytes
putCommandParser :: Parser (IO ())
putCommandParser = do
  Maybe ProjectPath
projectMaybe <- Parser ProjectPath -> Parser (Maybe ProjectPath)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser ProjectPath
projectOption
  Text
name <- Parser Text
nameOption
  FilePath
file <- Parser FilePath
fileOption
  pure do
    RIO (HerculesClientToken, HerculesClientEnv) () -> IO ()
forall b. RIO (HerculesClientToken, HerculesClientEnv) b -> IO b
runAuthenticated do
      ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient <- Maybe ProjectPath
-> RIO
     (HerculesClientToken, HerculesClientEnv)
     (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe
      ByteString
bytes <- case FilePath
file of
        FilePath
"-" -> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall (m :: * -> *). MonadIO m => m ByteString
BS.getContents
        FilePath
_ -> FilePath -> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall (m :: * -> *). MonadIO m => FilePath -> m ByteString
BS.readFile FilePath
file
      NoContent
_ :: NoContent <- Text
-> (Token -> ClientM NoContent)
-> RIO (HerculesClientToken, HerculesClientEnv) NoContent
forall b r.
(NFData b, Has HerculesClientToken r, Has HerculesClientEnv r) =>
Text -> (Token -> ClientM b) -> RIO r b
retryOnFail Text
"state put" do
        ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
   :- (Summary "Upload a state file"
       :> ("state"
           :> (Capture' '[Required, Strict] "stateName" Text
               :> ("data"
                   :> (StreamBody NoFraming OctetStream (SourceIO RawBytes)
                       :> (ClientAuth :> Put '[JSON] NoContent))))))
forall auth f.
ProjectStateResourceGroup auth f
-> f
   :- (Summary "Upload a state file"
       :> ("state"
           :> (Capture' '[Required, Strict] "stateName" Text
               :> ("data"
                   :> (StreamBody NoFraming OctetStream (SourceIO RawBytes)
                       :> (auth :> Put '[JSON] NoContent))))))
putStateData ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient Text
name ([RawBytes] -> SourceIO RawBytes
forall a (m :: * -> *). [a] -> SourceT m a
Servant.source [ByteString -> RawBytes
RawBytes ByteString
bytes])
      Text -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => Text -> m ()
putErrText (Text -> RIO (HerculesClientToken, HerculesClientEnv) ())
-> Text -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall a b. (a -> b) -> a -> b
$ Text
"hci: State file upload successful for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name

nameOption :: Optparse.Parser Text
nameOption :: Parser Text
nameOption = Mod OptionFields Text -> Parser Text
forall s. IsString s => Mod OptionFields s -> Parser s
strOption (Mod OptionFields Text -> Parser Text)
-> Mod OptionFields Text -> Parser Text
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"name" Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"NAME" Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Name of the state file"

fileOption :: Optparse.Parser FilePath
fileOption :: Parser FilePath
fileOption = Mod OptionFields FilePath -> Parser FilePath
forall s. IsString s => Mod OptionFields s -> Parser s
strOption (Mod OptionFields FilePath -> Parser FilePath)
-> Mod OptionFields FilePath -> Parser FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"file" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"FILE" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Local path of the state file or - for stdio" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> Completer -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasCompleter f => Completer -> Mod f a
completer (FilePath -> Completer
bashCompleter FilePath
"file")

versionOption :: Optparse.Parser Int
versionOption :: Parser Int
versionOption = ReadM Int -> Mod OptionFields Int -> Parser Int
forall a. ReadM a -> Mod OptionFields a -> Parser a
option ReadM Int
forall a. Read a => ReadM a
auto (Mod OptionFields Int -> Parser Int)
-> Mod OptionFields Int -> Parser Int
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"version" Mod OptionFields Int
-> Mod OptionFields Int -> Mod OptionFields Int
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"INT" Mod OptionFields Int
-> Mod OptionFields Int -> Mod OptionFields Int
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Version of the state file to retrieve"

getProjectAndClient :: (Has HerculesClientToken r, Has HerculesClientEnv r) => Maybe ProjectPath -> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient :: forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe =
  case Maybe ProjectPath
projectMaybe of
    Just ProjectPath
projectPath ->
      ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
    -> ToServant
         (ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
       (a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
   :- Substitute
        ("site"
         :> (Capture' '[Required, Strict] "site" (Name Forge)
             :> ("account"
                 :> (Capture' '[Required, Strict] "account" (Name Account)
                     :> ("project"
                         :> (Capture' '[Required, Strict] "project" (Name Project)
                             :> Placeholder))))))
        (ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
   :- Substitute
        ("site"
         :> (Capture' '[Required, Strict] "site" (Name Forge)
             :> ("account"
                 :> (Capture' '[Required, Strict] "account" (Name Account)
                     :> ("project"
                         :> (Capture' '[Required, Strict] "project" (Name Project)
                             :> Placeholder))))))
        (ToServantApi (ProjectStateResourceGroup auth))
byProjectName StateAPI ClientAuth (AsClientT ClientM)
api (Text -> Name Forge
forall k (a :: k). Text -> Name a
Name (Text -> Name Forge) -> Text -> Name Forge
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathSite ProjectPath
projectPath) (Text -> Name Account
forall k (a :: k). Text -> Name a
Name (Text -> Name Account) -> Text -> Name Account
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathOwner ProjectPath
projectPath) (Text -> Name Project
forall k (a :: k). Text -> Name a
Name (Text -> Name Project) -> Text -> Name Project
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathProject ProjectPath
projectPath))
    Maybe ProjectPath
Nothing -> do
      (Maybe (Id Project)
projectIdMaybe, ProjectPath
projectPath) <- RIO r (Maybe (Id Project), ProjectPath)
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
RIO r (Maybe (Id Project), ProjectPath)
findProjectContextually
      case Maybe (Id Project)
projectIdMaybe of
        Just Id Project
projectId ->
          ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
    -> ToServant
         (ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
       (a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
   :- Substitute
        ("projects"
         :> (Capture' '[Required, Strict] "projectId" (Id Project)
             :> Placeholder))
        (ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
   :- Substitute
        ("projects"
         :> (Capture' '[Required, Strict] "projectId" (Id Project)
             :> Placeholder))
        (ToServantApi (ProjectStateResourceGroup auth))
byProjectId StateAPI ClientAuth (AsClientT ClientM)
api Id Project
projectId)
        Maybe (Id Project)
Nothing ->
          ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
    -> ToServant
         (ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
       (a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
   :- Substitute
        ("site"
         :> (Capture' '[Required, Strict] "site" (Name Forge)
             :> ("account"
                 :> (Capture' '[Required, Strict] "account" (Name Account)
                     :> ("project"
                         :> (Capture' '[Required, Strict] "project" (Name Project)
                             :> Placeholder))))))
        (ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
   :- Substitute
        ("site"
         :> (Capture' '[Required, Strict] "site" (Name Forge)
             :> ("account"
                 :> (Capture' '[Required, Strict] "account" (Name Account)
                     :> ("project"
                         :> (Capture' '[Required, Strict] "project" (Name Project)
                             :> Placeholder))))))
        (ToServantApi (ProjectStateResourceGroup auth))
byProjectName StateAPI ClientAuth (AsClientT ClientM)
api (Text -> Name Forge
forall k (a :: k). Text -> Name a
Name (Text -> Name Forge) -> Text -> Name Forge
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathSite ProjectPath
projectPath) (Text -> Name Account
forall k (a :: k). Text -> Name a
Name (Text -> Name Account) -> Text -> Name Account
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathOwner ProjectPath
projectPath) (Text -> Name Project
forall k (a :: k). Text -> Name a
Name (Text -> Name Project) -> Text -> Name Project
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathProject ProjectPath
projectPath))