Skip to content

Commit

Permalink
[ADP-3320] Create skeleton HTTP API for Deposit Wallet (#4493)
Browse files Browse the repository at this point in the history
This pull request sketches how the `Cardano.Wallet.Deposit.HTTP` modules
related to the `Cardano.Wallet.Deposit.IO` modules.

### Issue number

ADP-3320
  • Loading branch information
HeinrichApfelmus authored Mar 26, 2024
2 parents 18fe1bf + 566f74f commit 24e341b
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 8 deletions.

This file was deleted.

20 changes: 17 additions & 3 deletions lib/customer-deposit-wallet/customer-deposit-wallet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ category: Web

extra-source-files:
spec/**/*.lagda.md
spec/**/*.openapi.yaml

common language
default-language:
Expand Down Expand Up @@ -81,7 +82,7 @@ test-suite unit
, cardano-wallet:cardano-wallet
, cardano-wallet-primitive
, cardano-wallet-test-utils
, customer-deposit-wallet:customer-deposit-wallet
, customer-deposit-wallet
, hspec >=2.8.2
, QuickCheck
, with-utf8
Expand All @@ -92,11 +93,24 @@ test-suite unit

library customer-deposit-wallet-http
import: language, opts-lib
hs-source-dirs: api/http
hs-source-dirs: http
build-depends:
, customer-deposit-wallet:customer-deposit-wallet
, aeson
, base
, customer-deposit-wallet
, memory
, servant
, servant-server
, text
, text-class
, warp
exposed-modules:
Cardano.Wallet.Deposit.HTTP
Cardano.Wallet.Deposit.HTTP.Endpoints
Cardano.Wallet.Deposit.HTTP.Implementation
Cardano.Wallet.Deposit.HTTP.Types.API
Cardano.Wallet.Deposit.HTTP.Types.JSON
Cardano.Wallet.Deposit.HTTP.Types.JSON.Encoding

executable customer-deposit-wallet
import: language, opts-exe
Expand Down
34 changes: 34 additions & 0 deletions lib/customer-deposit-wallet/http/Cardano/Wallet/Deposit/HTTP.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- |
-- Copyright: © 2024 Cardano Foundation
-- License: Apache-2.0
--
-- HTTP API for the wallet.
--
module Cardano.Wallet.Deposit.HTTP
( serveHTTP
)
where

import Prelude

import Cardano.Wallet.Deposit.HTTP.Implementation
( api
, implementation
)
import Servant.Server
( Application
, serve
)

import qualified Cardano.Wallet.Deposit.IO as Wallet
import qualified Network.Wai.Handler.Warp

{-----------------------------------------------------------------------------
Serving our HTTP API
------------------------------------------------------------------------------}

app :: Wallet.WalletInstance -> Application
app = serve api . implementation

serveHTTP :: Wallet.WalletInstance -> IO ()
serveHTTP = Network.Wai.Handler.Warp.run 8090 . app
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
-- |
-- Copyright: © 2024 Cardano Foundation
-- License: Apache-2.0
--
-- HTTP endpoints.
--
-- Each HTTP endpoint corresponds to a single function from the
-- "Cardano.Wallet.Deposit.IO" module.
--
module Cardano.Wallet.Deposit.HTTP.Endpoints
( listCustomers
, createAddress
) where

import Prelude

import Cardano.Wallet.Deposit.HTTP.Types.JSON
( Address
, ApiT (..)
, Customer
, CustomerList
)
import Control.Monad.IO.Class
( liftIO
)
import Servant.Server
( Handler
)

import qualified Cardano.Wallet.Deposit.IO as Wallet

{-----------------------------------------------------------------------------
HTTP Wallet endpoints
------------------------------------------------------------------------------}
{- NOTE [ApiT]
The arguments and results of the 'Handler's defined in the present module
are all wrapped in the 'ApiT' type constructor.
The purpose of the 'ApiT' type constructor is to control the
JSON encoding of the wrapped data type.
Specifically, we for any data type @X@ that we define a separate package,
we define instances such as 'FromJSON'@ (ApiT X)@ in the present package.
In other words, defining the JSON encoding for data types is a concern
of the @Cardano.Wallet.Deposit.HTTP.*@ modules;
it's not a concern of the modules where the data type is originally defined.
The JSONS instances for @ApiT@ can be implemented "by hand"
or they can be derived automatically from the definition (using e.g. Generics).
We plan on using FineTypes to generate the JSON encoding automatically.
-}

listCustomers
:: Wallet.WalletInstance
-> Handler (ApiT CustomerList)
listCustomers w =
liftIO $ ApiT <$> Wallet.listCustomers w

createAddress
:: Wallet.WalletInstance
-> ApiT Customer
-> Handler (ApiT Address)
createAddress w a =
liftIO $ ApiT <$> Wallet.createAddress (unApiT a) w
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- |
-- Copyright: © 2024 Cardano Foundation
-- License: Apache-2.0
--
-- Implementation of our HTTP API.
--
module Cardano.Wallet.Deposit.HTTP.Implementation
( api
, implementation
)
where

import Cardano.Wallet.Deposit.HTTP.Types.API
( API
)
import Data.Proxy
( Proxy (..)
)
import Servant
( (:<|>) (..)
)
import Servant.Server
( Server
)

import qualified Cardano.Wallet.Deposit.HTTP.Endpoints as HTTP
import qualified Cardano.Wallet.Deposit.IO as Wallet

{-----------------------------------------------------------------------------
Types
------------------------------------------------------------------------------}
api :: Proxy API
api = Proxy

implementation :: Wallet.WalletInstance -> Server API
implementation w =
HTTP.listCustomers w
:<|> HTTP.createAddress w
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Copyright: © 2024 Cardano Foundation
-- License: Apache-2.0
--
-- Servant Type for our HTTP API.
--
module Cardano.Wallet.Deposit.HTTP.Types.API
( API
)
where

import Cardano.Wallet.Deposit.HTTP.Types.JSON
( Address
, ApiT
, Customer
, CustomerList
)
import Servant.API
( Capture
, JSON
, StdMethod (..)
, Verb
, (:<|>)
, (:>)
)

{-----------------------------------------------------------------------------
API
------------------------------------------------------------------------------}

type API =
"customers"
:> Verb 'GET 200 '[JSON] (ApiT CustomerList)
:<|>
"customers"
:> Capture "customerId" (ApiT Customer)
:> Verb 'PUT 200 '[JSON] (ApiT Address)
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneDeriving #-}

-- |
-- Copyright: © 2024 Cardano Foundation
-- License: Apache-2.0
--
-- Data types with a JSON schema.
--
module Cardano.Wallet.Deposit.HTTP.Types.JSON
( ApiT (..)

-- * Re-exports
, Address
, Customer
, CustomerList
)
where

import Prelude

import Cardano.Wallet.Deposit.HTTP.Types.JSON.Encoding
( ViaText (..)
, customOptions
)
import Cardano.Wallet.Deposit.Pure
( Customer
)
import Cardano.Wallet.Deposit.Read
( Address
)
import Data.Aeson
( FromJSON (..)
, ToJSON (..)
, genericParseJSON
, genericToJSON
)
import Data.Bifunctor
( bimap
, first
)
import Data.Text
( Text
)
import Data.Text.Class
( FromText (..)
, getTextDecodingError
)
import Servant
( FromHttpApiData (..)
)

import qualified Data.Text as T

{-----------------------------------------------------------------------------
Additional type definitions
------------------------------------------------------------------------------}

type CustomerList = [(Customer, Address)]

{-----------------------------------------------------------------------------
ApiT
------------------------------------------------------------------------------}

newtype ApiT a = ApiT {unApiT :: a}
deriving (Eq, Ord, Show)

{-----------------------------------------------------------------------------
JSON encodings
------------------------------------------------------------------------------}

-- Address
deriving via ViaText Address instance FromJSON (ApiT Address)
deriving via ViaText Address instance ToJSON (ApiT Address)

-- Customer
instance FromHttpApiData (ApiT Customer) where
parseUrlPiece = fmap ApiT . fromText'

instance FromJSON (ApiT Customer) where
parseJSON = fmap ApiT . parseJSON

instance ToJSON (ApiT Customer) where
toJSON = toJSON . unApiT

-- | 'fromText' but with a simpler error type.
fromText' :: FromText a => Text -> Either Text a
fromText' = first (T.pack . getTextDecodingError) . fromText

-- CustomerList
type ApiCustomerList = [(ApiT Customer, ApiT Address)]

toApiCustomerList :: ApiT CustomerList -> ApiCustomerList
toApiCustomerList = fmap (bimap ApiT ApiT) . unApiT

fromApiCustomerList :: ApiCustomerList -> ApiT CustomerList
fromApiCustomerList = ApiT . fmap (bimap unApiT unApiT)

instance FromJSON (ApiT CustomerList) where
parseJSON = fmap fromApiCustomerList . genericParseJSON customOptions

instance ToJSON (ApiT CustomerList) where
toJSON = genericToJSON customOptions . toApiCustomerList
Loading

0 comments on commit 24e341b

Please sign in to comment.